Skip to main content

Tests PyPI Python Version

py-atc-ble-oepl

Python library for interacting with ATC BLE firmware over Bluetooth Low Energy.

Installation

uv add py-atc-ble-oepl

For 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 scan

CLI

atc-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.

Python API

Discover devices

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")

Upload an image

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

Image options

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
    )

Read device info

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}")

Pass a discovered device to avoid re-scanning

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].device) as device:
        await device.upload_image("photo.jpg")

Orientation

device.width and device.height are the size the panel displays, which is what you draw for. They are a quarter turn from the buffer the tag is actually sent, and upload_image rotates your image into that buffer for you.

The turn is a property of the hardware, not of any one model: a block written at buffer pixel (0,0) appears at the physical top right, with the buffer's X axis running physically downward. That was measured on four tags covering both wh_inverted_ble values, two resolutions, both panel orientations and both colour schemes.

It matters because getting it wrong is invisible from the code: the buffer is the right size either way, so nothing fails and the tag renders a full, deliberate-looking image that is simply sideways. If you are comparing against ATC_BLE_OEPL_Image_Upload.html, note it leaves this to the person drawing on the canvas, so a straight port of its pixel loop is a quarter turn out.

API reference

ATCDevice

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

discover_atc_devices(timeout=30.0)

Scans for ATC BLE devices (manufacturer ID 0x1337). Returns list[DiscoveredDevice].

DeviceCapabilities

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

DeviceConfig

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

Acknowledgements

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.

Development

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

Metadata

Release files for py-atc-ble-oepl 0.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-atc-ble-oepl 0.5.2
File Size Uploaded
py_atc_ble_oepl-0.5.2.tar.gz 107.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for py-atc-ble-oepl 0.5.2
File Interpreter ABI Platform
py_atc_ble_oepl-0.5.2-py3-none-any.whl Python 3 none any Details

Total release size: 147.2 kB

Release files / py_atc_ble_oepl-0.5.2.tar.gz

Download URL py_atc_ble_oepl-0.5.2.tar.gz
Size 107.7 kB
Tags Source
SHA-256 checksum
How to use checksums
c38bae8aaf51c631fb39ddbaef6217d81210b0a87adfdaa7931139ebae56660e
BLAKE2b-256 checksum
How to use checksums
3f95070932cbd11384420322d9b807dc617e182e58e2d66b3ef615ac4476ddad
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 Aug 28, 2026.

Transparency log

Release files / py_atc_ble_oepl-0.5.2-py3-none-any.whl

Download URL py_atc_ble_oepl-0.5.2-py3-none-any.whl
Size 39.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
21f4c1b50e48bb93f1400771c221e6ffc977935b66d6eb124e4ec658990133f6
BLAKE2b-256 checksum
How to use checksums
bccb9deddb825f63c1739ab60661504868fc99844e95d1e164b1ace96ef0747c
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 Aug 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.2 This release

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page