Python library for interacting with ATC BLE firmware over Bluetooth Low Energy.
uv add py-atc-ble-oeplFor the CLI:
uv add "py-atc-ble-oepl[cli]"Or run the CLI directly without installing into a project:
uvx --from "py-atc-ble-oepl[cli]" atc-ble scanatc-ble scan [--timeout 30] [--json]
atc-ble info --device ADDR [--timeout 60] [--json]
atc-ble led --device ADDR [--duration 5]
atc-ble upload --device ADDR IMAGE [--dither-mode burkes] [--fit contain] [--rotate 0] [--no-compress]
Scan for nearby devices:
$ atc-ble scan
┌──────────────────────────────────────┬───────────────┬──────────┐
│ Address │ Name │ RSSI │
├──────────────────────────────────────┼───────────────┼──────────┤
│ 5F4CEF52-A1CD-E2EE-011F-F27129B8D4A9 │ ATC_911943 │ -61 dBm │
└──────────────────────────────────────┴───────────────┴──────────┘
Show device info:
$ atc-ble info --device 5F4CEF52-A1CD-E2EE-011F-F27129B8D4A9
5F4CEF52-A1CD-E2EE-011F-F27129B8D4A9
├── Display
│ ├── Resolution 184 × 384
│ └── Color BWY
└── Hardware
├── OEPL type 0x0060
├── Screen type 2 (350 HS BWY UC Inverted)
└── ...
Flash the LED to identify a device physically:
$ atc-ble led --device 5F4CEF52-A1CD-E2EE-011F-F27129B8D4A9
LED done → ATC_911943
Upload an image:
$ atc-ble upload --device 5F4CEF52-... photo.jpg
Upload complete.
ATC tags advertise infrequently — the default --timeout for device commands is 60 s. On macOS the address is a UUID, not a MAC address.
from py_atc_ble_oepl import discover_atc_devices
devices = await discover_atc_devices(timeout=30.0)
for d in devices:
print(f"{d.name} {d.mac_address} {d.rssi} dBm")from py_atc_ble_oepl import ATCDevice
async with ATCDevice("AA:BB:CC:DD:EE:FF") as device:
success = await device.upload_image("photo.jpg")upload_image accepts a file path (str), raw bytes, or a PIL Image. It automatically:
- queries device capabilities (dimensions, color scheme)
- resizes and fits the image
- dithers to the display's color palette (MONO / BWR / BWY / BWRY)
- compresses and uploads over BLE
from py_atc_ble_oepl import ATCDevice, FitMode, Rotation
from epaper_dithering import DitherMode
async with ATCDevice("AA:BB:CC:DD:EE:FF") as device:
await device.upload_image(
"photo.jpg",
dither_mode=DitherMode.BURKES, # default
fit=FitMode.COVER, # STRETCH / CONTAIN / COVER / CROP
rotate=Rotation.ROTATE_90, # 0 / 90 / 180 / 270
compress=True, # default
)from py_atc_ble_oepl import ATCDevice
async with ATCDevice("AA:BB:CC:DD:EE:FF") as device:
caps = device._capabilities # DeviceCapabilities
cfg = device.device_config # DeviceConfig (full hardware settings)
print(f"{caps.width}x{caps.height} {caps.color_scheme}")
print(f"OEPL type 0x{cfg.hw_type:04X} screen_type {cfg.screen_type}")On macOS bleak can only connect to a device it has already seen during a scan. Passing the BLEDevice object from discovery skips a second scan:
from py_atc_ble_oepl import discover_atc_devices, ATCDevice
devices = await discover_atc_devices(timeout=30.0)
if devices:
async with ATCDevice(devices[0].mac_address, ble_device=devices[0].ble_device) as device:
await device.upload_image("photo.jpg")ATCDevice(mac_address, ble_device=None, auto_interrogate=True, connection_timeout=60.0)| Method / Property | Description |
|---|---|
async interrogate() |
Query device capabilities (called automatically on connect) |
async flash_led(duration=5.0) |
Flash the LED for duration seconds to identify the device |
async upload_image(image, ...) |
Upload and display an image |
width, height |
Display dimensions in pixels (None before interrogation) |
color_scheme |
ColorScheme enum value (None before interrogation) |
device_config |
DeviceConfig dataclass with full hardware settings |
Scans for ATC BLE devices (manufacturer ID 0x1337). Returns list[DiscoveredDevice].
| Field | Type | Description |
|---|---|---|
width |
int |
Display width in pixels |
height |
int |
Display height in pixels |
color_scheme |
int |
0=MONO, 1=BWR, 2=BWY, 3=BWRY |
Full hardware configuration from the 0011 dynamic config read. Key fields:
| Field | Type | Description |
|---|---|---|
hw_type |
int |
OEPL tag type (hex) |
screen_type |
int |
ATC screen driver type (1–47) |
screen_w, screen_h |
int |
Physical display dimensions |
screen_colors |
int |
Color count |
black_invert, second_color_invert |
bool |
Color plane polarity |
epd_pinout, led_pinout, nfc_pinout, flash_pinout |
dataclass or None |
GPIO pin assignments |
This library is based on the work of Aaron (atc1441) - creator of the ATC BLE firmware and the original web uploader for ATC BLE e-paper tags. The BLE protocol implemented here is derived entirely from that web uploader.
git clone https://github.com/OpenDisplay-org/py-atc-ble-oepl.git
cd py-atc-ble-oepl
uv sync --all-extras
uv run pytest tests/ -v
uv run ruff check .
uv run mypy src/py_atc_ble_oepl