Cloud-side service for converting uploaded images into a compact 6-color e-paper format and serving them to an ESP32 over outbound HTTP.
The ESP32 does not need a public IP. It wakes up, requests the current manifest, downloads the binary only when the version changes, reports status, and sleeps.
GET /api/devices/{device_id}/current
GET /api/images/{image_id}/data
POST /api/devices/{device_id}/status
Image data uses epd4bit-indexed-v1: two pixels per byte, high nibble first.
Palette indexes are:
0 black
1 white
2 yellow
3 red
4 blue
5 green
For both 800x480 and 480x800, the binary payload is 192000 bytes.
InkSplash app clients use email/password accounts plus Bearer tokens:
POST /api/auth/register
POST /api/auth/login
POST /api/auth/oauth/apple
POST /api/auth/oauth/google
GET /api/me
POST /api/auth/verify-email/request
POST /api/auth/verify-email/confirm
POST /api/auth/password-reset/request
POST /api/auth/password-reset/confirm
App users can claim devices, share devices with family members, upload images,
assign their own images to devices where they are owner or admin, and read
device status history:
POST /api/me/devices/claim
GET /api/me/devices
GET /api/me/devices/{device_id}
PATCH /api/me/devices/{device_id}
DELETE /api/me/devices/{device_id}
POST /api/me/devices/{device_id}/assign
POST /api/me/devices/{device_id}/invites
POST /api/me/device-invites/accept
GET /api/me/devices/{device_id}/members
DELETE /api/me/devices/{device_id}/members/{user_id}
GET /api/me/devices/{device_id}/status-events
GET /api/me/groups
POST /api/me/groups
PATCH /api/me/groups/{group_id}
DELETE /api/me/groups/{group_id}
GET /api/me/groups/{group_id}/members
POST /api/me/groups/{group_id}/invites
POST /api/me/group-invites/accept
GET /api/me/groups/{group_id}/devices
POST /api/me/groups/{group_id}/devices
DELETE /api/me/groups/{group_id}/devices/{device_id}
The mobile UI also uses personal media, timeline, notifications, storage, profile, and preferences APIs:
GET /api/me/albums
POST /api/me/albums
GET /api/me/albums/{album_id}
PATCH /api/me/albums/{album_id}
DELETE /api/me/albums/{album_id}
POST /api/me/albums/{album_id}/photos/{photo_id}
DELETE /api/me/albums/{album_id}/photos/{photo_id}
GET /api/me/photos
GET /api/me/photos/{photo_id}
PATCH /api/me/photos/{photo_id}
GET /api/me/timeline
GET /api/me/notifications
PATCH /api/me/notifications/{notification_id}/read
GET /api/me/notification-settings
PATCH /api/me/notification-settings
GET /api/me/storage
GET /api/me/profile
PATCH /api/me/profile
GET /api/me/preferences
PATCH /api/me/preferences
Bearer image uploads automatically create photo records. Albums and photos
are scoped to the current user. Timeline events include uploads, assignments,
device status/display events, group/member events, and album updates.
User write operations require verified email. The ESP32 protocol above remains token-based and does not require an app account.
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
export EPAPER_ADMIN_TOKEN=dev-admin-token
export EPAPER_AUTH_SECRET=dev-auth-secret
uvicorn app.main:app --reloadOpen http://127.0.0.1:8000/health.
Create or reset a device:
curl -X POST http://127.0.0.1:8000/api/devices/device001 \
-H 'Content-Type: application/json' \
-H 'X-Admin-Token: dev-admin-token' \
-d '{}'Upload an image:
curl -X POST http://127.0.0.1:8000/api/images \
-H 'X-Admin-Token: dev-admin-token' \
-F 'file=@/path/to/image.jpg' \
-F 'direction=auto' \
-F 'mode=scale' \
-F 'dither=true'Assign an uploaded image to a device:
curl -X POST http://127.0.0.1:8000/api/devices/device001/assign \
-H 'Content-Type: application/json' \
-H 'X-Admin-Token: dev-admin-token' \
-d '{"image_id":"IMAGE_ID_FROM_UPLOAD"}'Simulate an ESP32:
python3 simulate_device.py \
--server http://127.0.0.1:8000 \
--device-id device001 \
--token DEVICE_TOKEN_FROM_CREATEOn an Alibaba Cloud ECS Ubuntu instance with a public IP:
sudo apt update
sudo apt install -y python3 python3-venv python3-pip nginx
sudo adduser --system --group --home /opt/ePaperService epaper
sudo mkdir -p /opt/ePaperService /var/lib/epaper-service
sudo chown -R epaper:epaper /opt/ePaperService /var/lib/epaper-serviceUse Ubuntu 22.04 or newer, or any image with Python 3.10+. Copy this project
into /opt/ePaperService, then:
cd /opt/ePaperService
sudo -u epaper python3 -m venv .venv
sudo -u epaper .venv/bin/pip install -r requirements.txt
sudo cp scripts/epaper.service /etc/systemd/system/epaper.service
sudo systemctl daemon-reload
sudo systemctl enable --now epaperPut nginx in front of uvicorn:
sudo cp scripts/nginx.conf /etc/nginx/sites-available/epaper
sudo ln -s /etc/nginx/sites-available/epaper /etc/nginx/sites-enabled/epaper
sudo nginx -t
sudo systemctl reload nginxFor first cloud testing, HTTP on port 80 is enough; switch to HTTPS before long-term deployment. In the Alibaba Cloud security group, open inbound TCP 80 for HTTP testing and TCP 443 after HTTPS is configured.
Production app builds should use an HTTPS base URL, for example:
https://api.example.com
Set these server environment variables:
EPAPER_ADMIN_TOKEN=...
EPAPER_AUTH_SECRET=...
DEVICE_CLAIM_HMAC_SECRET=...
APPLE_CLIENT_ID=com.inksplash.app
GOOGLE_CLIENT_ID=...
PUBLIC_APP_URL=https://api.example.com
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=...
SMTP_PASSWORD=...
SMTP_FROM=no-reply@example.com
SMTP_USE_TLS=1
If SMTP is not configured, verification/reset/invite codes are written to the service log for local testing. Do not rely on log-based codes in production.
Apple and Google OAuth endpoints verify identity tokens server-side. Set the client id values that match the mobile app provider configuration; multiple ids can be supplied as comma-separated values.
Run the test suite locally:
.venv/bin/pytest -qFor HTTPS, put nginx in front of uvicorn with a certificate from your preferred CA, listen on 443, and redirect port 80 to 443. Do not store real secrets in repo files.
Backups:
sudo EPAPER_DATA_DIR=/var/lib/epaper-service \
EPAPER_BACKUP_DIR=/var/backups/epaper-service \
/opt/ePaperService/scripts/backup.shRecommended firmware behavior:
- Wake from deep sleep.
- Connect Wi-Fi.
GET /api/devices/{device_id}/currentwithX-Device-Token.- If
versionis unchanged, postunchangedstatus and sleep. - If changed, download
download_url. - Verify payload size and
sha256. - Convert 4-bit palette indexes into the display driver's buffer.
- Refresh the screen, post
displayedorerror, then sleep.