Skip to main content

Async Python client for the OpenDisplay AP (OpenEPaperLink)

Project description

oepl

Async Python client for the OpenEPaperLink Access Point (AP).

  • Full async/await API via aiohttp
  • Live tag updates over WebSocket
  • Image upload with optional client-side dithering
  • Raw image download and decoding (G5, zlib, bitmap)
  • LED flash control
  • CLI for interactive use

Installation

pip install py-oepl

With client-side dithering support:

pip install py-oepl epaper-dithering

Quick start

import asyncio
from oepl import OEPLClient

async def main():
    async with OEPLClient("192.168.1.100") as client:
        tags = await client.get_tags()
        for tag in tags:
            print(tag.mac, tag.alias, tag.battery_mv, "mV")

asyncio.run(main())

CLI

Set OEPL_HOST to avoid passing --host every time:

export OEPL_HOST=192.168.1.100

List tags

oepl --host 192.168.1.100 tags
oepl tags --json          # machine-readable JSON
oepl tags --watch         # live stream via WebSocket

AP info

oepl ap                   # hardware info + current config
oepl ap --json

Upload an image

oepl upload AABBCCDDEEFF image.png
oepl upload AABBCCDDEEFF image.png --lut fast --rotate 90 --ttl 300

--lut choices: default, no-repeat, fast-no-reds, fast --rotate choices: 0, 90, 180, 270 --ttl is in seconds; 0 lets the AP use the tag's default sleep interval.

Send a command

oepl cmd AABBCCDDEEFF refresh
oepl cmd AABBCCDDEEFF clear
oepl cmd AABBCCDDEEFF reboot
oepl cmd AABBCCDDEEFF scan

Flash LEDs

oepl led AABBCCDDEEFF --color 255 0 0
oepl led AABBCCDDEEFF --color 0 255 0 --flash-speed 0.5 --flash-count 3
oepl led AABBCCDDEEFF --color 0 0 255 --brightness 3 --repeats 4

Download and decode the stored image

oepl get-image AABBCCDDEEFF              # prints decoded JPEG to stdout
oepl get-image AABBCCDDEEFF -o out.jpg   # save to file

Tag type definitions are fetched directly from the AP — no internet access required.

Python API

OEPLClient

from oepl import OEPLClient

client = OEPLClient(
    host="192.168.1.100",
    session=None,            # optional: supply an existing aiohttp.ClientSession
    reconnect_interval=30.0, # seconds between WebSocket reconnect attempts
)

Use as an async context manager (recommended) or call connect() / disconnect() manually.

Tag operations

# Fetch all tags (paginated); populates internal cache; fires on_tag_update callbacks
tags: list[Tag] = await client.get_tags()

# Upload an image (PIL Image or raw bytes)
from PIL import Image
img = Image.open("label.png")
await client.upload_image(
    "AABBCCDDEEFF",
    img,
    ttl=300,             # seconds; 0 = tag default
    rotate=Rotation.R90,
    lut=LUT.FAST,
)

# Set the alias shown in the AP web UI
await client.set_alias("AABBCCDDEEFF", "my-display")

# Send a command
from oepl import TagCommand
await client.send_tag_cmd("AABBCCDDEEFF", TagCommand.REFRESH)

# Flash LEDs
from oepl.led import Color, LEDPattern, LEDSegment
pattern = LEDPattern([LEDSegment(Color(255, 0, 0))], repeats=3)
await client.set_led("AABBCCDDEEFF", pattern)

# Fetch the tag type definition (served by the AP, works offline)
tag_type = await client.get_tag_type(0x16)  # returns TagType | None

# Download and decode the stored image for a tag
from oepl import decode_image
raw = await client.get_image_raw("AABBCCDDEEFF")  # bytes | None
if raw and tag_type:
    jpeg_bytes = decode_image(raw, tag_type)

AP operations

info   = await client.get_sysinfo()    # APInfo — hardware/firmware
config = await client.get_ap_config()  # APConfig — current settings
await client.save_ap_config(config)
await client.set_time(int(time.time()))
await client.reboot_ap()

Live updates via WebSocket

async with OEPLClient("192.168.1.100") as client:
    client.on_tag_update(lambda tag: print("updated:", tag.mac))
    client.on_ap_status(lambda s: print("AP free heap:", s.free_heap))
    client.on_connection_change(lambda ok: print("connected:", ok))
    client.on_log(lambda msg: print("AP log:", msg))

    # Callbacks fire as WebSocket messages arrive.
    # on_tag_update also fires for each tag returned by get_tags().
    tags = await client.get_tags()
    await asyncio.sleep(60)

Each on_* method returns an unsubscribe callable:

unsub = client.on_tag_update(my_callback)
# later:
unsub()

Models

Class Key fields
Tag mac, alias, hw_type, last_seen, battery_mv, lqi, rssi, channel, content_mode, firmware_version
APInfo alias, env, build_version, ap_version, psram_size, flash_size, has_c6, has_ble
APConfig ap_channel, led, maxsleep, tz, preview, night_start, night_end
APStatus ip, heap, free_heap, run_status, temp, wifi_rssi, record_count
TagType type_id, width, height, bpp, color_table, short_lut

Enums

from oepl import LUT, Rotation, TagCommand
from oepl.enums import APState, RunStatus
Enum Values
LUT NO_REPEAT, DEFAULT, FAST_NO_REDS, FAST
Rotation NONE, R90, R180, R270
TagCommand CLEAR, REFRESH, REBOOT, SCAN
APState OFFLINE, ONLINE, FLASHING, WAIT_CHECKIN, WAIT_SETTIME, IDLE, DOWNLOADING, NO_RADIO

Exceptions

from oepl.exceptions import (
    OEPLError,           # base
    OEPLConnectionError, # could not reach AP
    OEPLTimeoutError,    # request timed out (after retries)
    OEPLNotFoundError,   # 404
    OEPLResponseError,   # other non-2xx (has .status and .body)
)

Image dithering

When epaper-dithering is installed, upload_image applies Floyd-Steinberg dithering automatically for BWR displays. Override with:

from oepl import DitherMode, ColorScheme

await client.upload_image(
    mac,
    pil_image,
    dither_mode=DitherMode.NONE,
    color_scheme=ColorScheme.BW,
)

DitherMode and ColorScheme are None when epaper-dithering is not installed.

Development

uv sync
uv run pytest

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_oepl-0.1.0.tar.gz (112.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_oepl-0.1.0-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

Details for the file py_oepl-0.1.0.tar.gz.

File metadata

  • Download URL: py_oepl-0.1.0.tar.gz
  • Upload date:
  • Size: 112.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for py_oepl-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a92c9de741933bed667d415ba1cdf000658dccca392c80ff9e7e2df6db78f7a4
MD5 c2b681422e37dcc89bf2a45f8c5b16a8
BLAKE2b-256 c103a255ed081093fb4f319e6e7d2348b06f822e2d04b610859c12697b390891

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_oepl-0.1.0.tar.gz:

Publisher: release.yml on g4bri3lDev/py-oepl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py_oepl-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: py_oepl-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for py_oepl-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 be82ee4b40944460fd64a3bd7e34ffc6de13e8c7e3b4844d04183104e8db124b
MD5 b0d64871b663df994b51b8219362a3bb
BLAKE2b-256 0b872ffeec4666a7a812c9fae78cf21dc04abacc67a51bc673b42ae1229a71f3

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_oepl-0.1.0-py3-none-any.whl:

Publisher: release.yml on g4bri3lDev/py-oepl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page