A Python library and CLI for controlling Bluetooth Low Energy LED name badges. Supports sending text, controlling brightness, animations, and includes a web-based font editor for creating custom characters.
- Scan for LED badge devices
- Send text with various scroll modes (static, left, right, up, down, snow)
- Control brightness and scroll speed
- Play built-in animations
- Interactive mode for real-time experimentation
- Web-based font editor with multi-width character support
- Python API for integration into other applications
# Install with poetry
poetry install
# Or install from local path
pip install /path/to/ble-led-badge
# Or install in development mode
pip install -e /path/to/ble-led-badge- Python 3.8+
- Bleak - Cross-platform BLE library
- pycryptodome - AES encryption for badge protocol
-
Find your badge:
badge-controller scan
-
Set your badge address:
export BADGE_ADDR="AA:BB:CC:DD:EE:FF"
-
Send text:
badge-controller text $BADGE_ADDR "Hello World"
# Scan for LED badges (filtered by service UUID and common name patterns)
badge-controller scan
# Scan for all BLE devices (unfiltered)
badge-controller scan --all
# Custom timeout (default 10 seconds)
badge-controller scan --timeout 20# Basic text (scrolls left by default)
badge-controller text $BADGE_ADDR "Hello World"
# Static text (no scrolling)
badge-controller text $BADGE_ADDR "Hi!" --scroll static
# Scroll right
badge-controller text $BADGE_ADDR "Welcome" --scroll right
# With custom brightness (0-255)
badge-controller text $BADGE_ADDR "Bright!" --brightness 255
# With custom speed (0-255, higher = faster)
badge-controller text $BADGE_ADDR "Fast scroll" --speed 100
# Combine options
badge-controller text $BADGE_ADDR "Custom" --scroll left --brightness 200 --speed 75badge-controller brightness $BADGE_ADDR 128
badge-controller brightness $BADGE_ADDR 255 # MaximumNote: Brightness is not saved between badge power cycles.
badge-controller speed $BADGE_ADDR 50
badge-controller speed $BADGE_ADDR 100 # FasterNote: Speed is not saved between badge power cycles.
badge-controller animation $BADGE_ADDR 1Available animations (1-8):
- Falling leaves turn into flashing word "love"
- Four animated hearts
- Cheers beer tankards
- The word "COME" builds with flashing face
- Two radiating and flashing hearts
- Animated dollar signs
- Two fish kissing
- Animal face with radiating thought waves
badge-controller image $BADGE_ADDR 1badge-controller check $BADGE_ADDRInteractive mode keeps a persistent BLE connection open, allowing you to send multiple commands without reconnecting each time.
badge-controller interactive $BADGE_ADDR| Command | Description |
|---|---|
text <message> |
Send text to display |
scroll <mode> |
Set scroll mode: static, left, right, up, down, snow |
brightness <0-255> |
Set brightness level |
speed <0-255> |
Set scroll speed |
animation <id> |
Play animation (1-8) |
image <id> |
Show stored image |
check |
Check stored images |
status |
Show current settings |
quit |
Exit interactive mode |
- Command chaining: Separate multiple commands with
;> scroll static; brightness 200; text Hello World - Command history: Use up/down arrows to recall previous commands
- Settings persistence: Scroll mode, brightness, and speed are remembered for subsequent text commands
$ badge-controller interactive AA:BB:CC:DD:EE:FF
Connected! Interactive mode.
Commands:
text <message> - Send text to display
scroll <mode> - Set scroll mode (static, left, right, up, down, snow)
brightness <0-255> - Set brightness
...
> brightness 200
OK
> scroll left
OK - scroll mode: left
> text Hello World
OK - sent: Hello World
> speed 100; text Fast scrolling!
OK
OK - sent: Fast scrolling!
> status
Scroll: left, Brightness: 200, Speed: 100
> quit
The badge controller can be imported into other Python applications:
from badge_controller import Badge, ScrollMode, TextRenderer, scan_for_badges
# Scan for badges
badges = await scan_for_badges()
for badge in badges:
print(f"{badge.name}: {badge.address}")
# Control a badge
async with Badge("AA:BB:CC:DD:EE:FF") as badge:
await badge.set_brightness(128)
await badge.send_text("Hello", scroll_mode=ScrollMode.LEFT)
await badge.play_animation(1)
# Extend the font with custom characters
TextRenderer.FONT['©'] = [0x1c, 0x22, 0x49, 0x45, 0x49, 0x22, 0x1c, 0x00, 0x00]
# Load custom font from JSON
import json
with open('my_font.json') as f:
TextRenderer.FONT.update(json.load(f))Badge- Main controller classscan_for_badges()- Scan for nearby badgesfind_badge_by_name()- Find badge by name patternTextRenderer- Text-to-bitmap rendering with font dataScrollMode- Enum for scroll modes (STATIC, LEFT, RIGHT, UP, DOWN, SNOW)Animation- Enum for animationsCommand- Low-level command builders
A web-based font editor is included for creating and editing character bitmaps.
Open font-editor/index.html in a web browser. No server required.
- Visual pixel editor - Click to toggle pixels on/off
- Character grid - Quick selection of all characters in the font
- Add/delete characters - Add new characters including emojis
- Multi-width characters - Create wide characters spanning multiple segments (e.g., for emojis)
- Live preview - See how text will appear on the badge
- JSON import/export - Save and load font files
Characters can span multiple standard widths (6 pixels each). This is useful for emojis or wide symbols:
- Enter a character and set the desired width
- Click "Add Character"
- The pixel grid expands to show multiple segments
- Design your character across all segments
- Save the JSON to persist your changes
The font data format:
- Single-width:
[b0, b1, ..., b8](9 bytes) - Multi-width:
[[b0...b8], [b0...b8], ...](array of 9-byte segments)
Character selection grid with multi-width emoji (3 segments), and the pixel editor showing the character design
Live preview of text, orientation display, and JSON data editor for saving/loading fonts
ble-led-badge/
├── badge_controller/ # Main Python package
│ ├── __init__.py # Package exports
│ ├── badge.py # Badge controller class
│ ├── cli.py # Command-line interface
│ ├── commands.py # Command packet builders
│ ├── encryption.py # AES-ECB encryption
│ ├── protocol.py # BLE UUIDs and constants
│ └── text_renderer.py # Text-to-bitmap conversion
├── osc_server/ # OSC server for creative tools
│ ├── server.py # OSC server with scan/install/run
│ └── README.md # OSC server documentation
├── font-editor/ # Web-based font editor
│ ├── index.html # Font editor application
│ └── font.json # Font data file
├── .github/workflows/ # CI/CD
│ └── build-arm64.yml # Build ARM64 binaries (Linux + macOS)
├── badge-osc-server.spec # PyInstaller spec for building binaries
├── examples/ # Example scripts
├── experiments/ # Experimental code
├── initial_analysis/ # Reverse engineering notes
├── images/ # Screenshots and images
├── pyproject.toml # Poetry configuration
└── README.md # This file
- Discovery: Scans for BLE devices advertising the badge service UUID (
0000fee9-0000-1000-8000-00805f9b34fb) or matching common name patterns - Connection: Establishes BLE connection using Bleak
- Text Rendering: Converts text to bitmap using the font (9 bytes per character segment)
- Encryption: Commands are AES-ECB encrypted before sending
- Communication: Data sent via GATT characteristics:
- Command channel (encrypted):
d44bc439-abfd-45a2-b575-925416129600 - Image upload (unencrypted):
d44bc439-abfd-45a2-b575-92541612960a - Notifications:
d44bc439-abfd-45a2-b575-925416129601
- Command channel (encrypted):
MIT