2026-10-02 00:03:48 +08:00
|
|
|
|
# Pigway Frame Flow
|
|
|
|
|
|
|
|
|
|
|
|
Pigway Frame Flow is a lightweight self-hosted platform for programmable smart-frame automation.
|
|
|
|
|
|
|
|
|
|
|
|
独立运行的智能画框服务:中英文 Web UI、深浅色模式、设备驱动抽象、KoKonna 全部已公开接口、图片处理、模板、SQLite 调度与播放列表、临时覆盖恢复和 REST API。
|
|
|
|
|
|
|
|
|
|
|
|
KoKonna is a supported device integration and is not affiliated with this project unless otherwise stated.
|
|
|
|
|
|
|
|
|
|
|
|
## Quick Start / 快速开始
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
python3 -m venv .venv
|
|
|
|
|
|
.venv/bin/python -m pip install -r requirements.lock
|
|
|
|
|
|
.venv/bin/python scripts/run_local.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Open http://127.0.0.1:18787. The local demo creates a private `.env` with a random
|
|
|
|
|
|
FRAME_FLOW_TOKEN. Read that token locally in an editor and enter it on the login
|
|
|
|
|
|
screen. Do not paste a KoKonna API Key into Web UI. Demo always forces mock mode
|
|
|
|
|
|
and disables real writes. It leaves data persistent in ./data and ./config.
|
|
|
|
|
|
|
2026-10-02 08:43:07 +08:00
|
|
|
|
网页使用账号密码登录。通过 `python scripts/create-account.py --username YOUR_NAME` 创建账号,再启动服务;密码不会写入代码或明文配置。勾选记住登录可保持 30 天,设置页面可修改密码或退出。
|
2026-10-02 00:03:48 +08:00
|
|
|
|
|
|
|
|
|
|
## Architecture / 架构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
providers → templates / renderers → generated image
|
|
|
|
|
|
↓
|
|
|
|
|
|
Web UI / external REST → DisplayService → FrameDeviceDriver → KokonnaDriver
|
|
|
|
|
|
Scheduler / Playlist ─────────────┘
|
|
|
|
|
|
SQLite: operations / history / jobs / overrides / assets
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Package: pigway_frame_flow. Device composition is in core/registry.py;
|
|
|
|
|
|
vendor-specific code is in drivers/kokonna. One process, one worker, no Redis,
|
|
|
|
|
|
message broker, OpenClaw, Home Assistant or external application dependency.
|
|
|
|
|
|
|
|
|
|
|
|
## Configuration / 配置
|
|
|
|
|
|
|
|
|
|
|
|
Copy `.env.example` to `.env`, set a strong FRAME_FLOW_TOKEN, chmod 600 .env.
|
|
|
|
|
|
- KOKONNA_MOCK=true: independent persistent demo, no cloud calls.
|
|
|
|
|
|
- KOKONNA_WRITE_ENABLED=false: live write protection, mandatory before verification.
|
|
|
|
|
|
- KOKONNA_API_KEY: device key, never stored in DB or returned to Web UI.
|
|
|
|
|
|
- *_FILE: optional Docker Secret equivalent.
|
|
|
|
|
|
- WEB_BIND/WEB_PORT: Compose host bind and port. Internal app binds container network.
|
|
|
|
|
|
- CORS_ALLOWED_ORIGINS: exact comma-separated origins; default empty.
|
|
|
|
|
|
- IMAGE_URL_ALLOWED_HOSTS: exact public HTTPS hosts; default disabled. Redirects denied.
|
|
|
|
|
|
- TZ=Asia/Shanghai; DATA_DIR=/data; CONFIG_DIR=/config.
|
|
|
|
|
|
|
|
|
|
|
|
For multiple devices copy deploy/devices.yml.example into config/devices.yml,
|
|
|
|
|
|
reference a separate environment key per device. Jobs bind a device_id. Layout
|
|
|
|
|
|
currently targets 1200 × 1600; incompatible geometry is refused before writes.
|
|
|
|
|
|
|
|
|
|
|
|
## Docker
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
cp .env.example .env
|
|
|
|
|
|
# Configure a token; keep API key empty, mock true, writes false.
|
|
|
|
|
|
mkdir -p data/images data/generated data/cache config
|
|
|
|
|
|
sudo chown -R 10333:10333 data config
|
|
|
|
|
|
./scripts/build-verify.sh
|
|
|
|
|
|
docker compose up -d
|
|
|
|
|
|
python3 scripts/smoke-test.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Image pigway-frame-flow:latest, service/container pigway-frame-flow, AMD64,
|
|
|
|
|
|
Python 3.14.5, pinned runtime dependencies, CJK fonts, non-root UID/GID 10333,
|
|
|
|
|
|
read-only root filesystem, writable /data and /config, no capabilities, no host
|
|
|
|
|
|
network, no Docker socket. Restart unless-stopped. JSON-file logs capped 10m × 3.
|
|
|
|
|
|
Healthcheck uses service /health, not cloud availability. No second log database.
|
|
|
|
|
|
|
|
|
|
|
|
## Synology NAS
|
|
|
|
|
|
|
2026-10-02 01:15:50 +08:00
|
|
|
|
Target `/volume2/docker/pigway-frame-flow/`. See [NAS deployment](docs/NAS.md).
|
2026-10-02 00:03:48 +08:00
|
|
|
|
Set WEB_BIND to the NAS LAN IP to restrict the published interface, or explicitly
|
|
|
|
|
|
0.0.0.0 when desired. Port 18787 must be free. No router/firewall/reverse proxy
|
|
|
|
|
|
rules are modified. Source and persistent volumes stay in this project directory.
|
|
|
|
|
|
No artificial logs directory is required. Compose creates its own bridge network.
|
|
|
|
|
|
|
|
|
|
|
|
## KoKonna Setup / 真实设备
|
|
|
|
|
|
|
|
|
|
|
|
First deploy Mock and verify persistence. Then set KOKONNA_MOCK=false and fill
|
|
|
|
|
|
KOKONNA_API_KEY through private environment/secret. Keep writes false. /ready checks
|
|
|
|
|
|
configuration/DB/worker/token without a cloud call. Query device and images metadata;
|
|
|
|
|
|
compare model, geometry, timezone, heartbeat, battery, imageId, coverId, sync.
|
|
|
|
|
|
If different, stop and report. **Do not enable writes or perform the single test
|
|
|
|
|
|
until the owner explicitly authorizes it.** Test design: [REAL_DEVICE_TEST.md](docs/REAL_DEVICE_TEST.md).
|
|
|
|
|
|
|
|
|
|
|
|
## Security
|
|
|
|
|
|
|
2026-10-02 08:47:33 +08:00
|
|
|
|
Protected /api/v1 capabilities accept an administrator account session, or external
|
|
|
|
|
|
Frame Flow Bearer auth with scope checks.
|
2026-10-02 00:03:48 +08:00
|
|
|
|
SQLite stores token hashes. Device keys live only in env/secret and runtime memory.
|
2026-10-02 08:43:07 +08:00
|
|
|
|
Browser login uses SQLite accounts with salted scrypt password hashes and revocable,
|
|
|
|
|
|
HttpOnly/SameSite=Strict cookies (30 days when remembered, 12 hours otherwise).
|
|
|
|
|
|
Mutations require a session CSRF header. Logout and password changes revoke sessions.
|
|
|
|
|
|
Passwords and browser sessions are never stored in localStorage. Bootstrap hashes
|
|
|
|
|
|
in private `config/accounts.json` seed new accounts only; restarting does not undo
|
|
|
|
|
|
a password change. Five failed logins per source IP cause a five-minute lockout.
|
|
|
|
|
|
The browser needs no API Token. Bearer authentication remains compatible for
|
|
|
|
|
|
external clients; the KoKonna key is never used for account login.
|
|
|
|
|
|
The LAN deployment uses HTTP; use an explicitly configured HTTPS endpoint if
|
|
|
|
|
|
transport encryption is needed. No reverse proxy or firewall is changed automatically.
|
2026-10-02 00:03:48 +08:00
|
|
|
|
|
|
|
|
|
|
## API
|
|
|
|
|
|
|
|
|
|
|
|
OpenAPI 3.x at /openapi.json; Swagger at /docs; stable /api/v1. Display/render/upload
|
|
|
|
|
|
return 202 + operation_id. Poll /api/v1/operations/{id}; completion requires matching
|
|
|
|
|
|
image ID and cloud synced flag. Idempotency-Key retained seven days. X-Request-ID
|
|
|
|
|
|
traces history. UNKNOWN writes block later mutations; read-only reconciliation
|
|
|
|
|
|
and explicit admin acknowledgement release the block, never replay a write.
|
|
|
|
|
|
The older /api/display route is retained and deprecated (same v1 payload).
|
|
|
|
|
|
See [external integration contract](docs/integrations/TICKTAR.md).
|
|
|
|
|
|
|
|
|
|
|
|
## Scheduler / Playback
|
|
|
|
|
|
|
|
|
|
|
|
Cron (5 fields), daily, weekdays, weekends, date and temporary tasks; Shanghai time.
|
|
|
|
|
|
Use source {"type":"restore"} for explicit restore jobs. Unique job/slot and persistent
|
|
|
|
|
|
idempotency prevent duplicate dispatch. Overdue tasks >5 minutes are skipped rather
|
|
|
|
|
|
than unexpectedly overwriting artwork after downtime. State persists across restart.
|
|
|
|
|
|
Overrides use priority then recency; lower priorities wait without uploading.
|
|
|
|
|
|
If another app changes the frame, expired overrides do not overwrite its artwork.
|
|
|
|
|
|
Frame Flow playlists support ordered/random cloud image IDs and >=5-minute intervals;
|
|
|
|
|
|
only one enabled playlist per device. They pause for overrides/unknown writes.
|
|
|
|
|
|
Native cloud slideshow mode/interval are readable; membership/editing are unavailable
|
|
|
|
|
|
in the official API. Avoid having native and Frame Flow playback compete.
|
|
|
|
|
|
|
|
|
|
|
|
## Templates / 图片
|
|
|
|
|
|
|
|
|
|
|
|
Six JSON layout templates: daily-dashboard, weather, calendar, quote, photo-text,
|
|
|
|
|
|
system-alert. color and grayscale profiles. Photo data accepts photo_id = local asset
|
|
|
|
|
|
ID. Mock weather is clearly labeled; no unreliable lunar date is fabricated.
|
|
|
|
|
|
Contain preserves composition with margins; cover crops centrally; smart is an
|
|
|
|
|
|
edge-saliency heuristic. Always inspect preview; face-aware guarantees are not claimed.
|
|
|
|
|
|
Cloud uploads activate immediately, so gallery-only means **local staging**.
|
|
|
|
|
|
|
|
|
|
|
|
## Development / Testing
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
.venv/bin/python -m pip install -e '.[test]'
|
|
|
|
|
|
.venv/bin/python -m pytest -q
|
|
|
|
|
|
.venv/bin/python -m ruff check pigway_frame_flow tests scripts
|
|
|
|
|
|
.venv/bin/python -m ruff format --check pigway_frame_flow tests scripts
|
|
|
|
|
|
.venv/bin/python -m mypy pigway_frame_flow
|
|
|
|
|
|
.venv/bin/python -m pip_audit -r requirements.lock --no-deps --disable-pip
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Tests mock every cloud request; no real-device writes. scripts/ui-smoke.cjs runs an
|
|
|
|
|
|
isolated headless browser and checks settings.mock before acting. CI builds AMD64,
|
|
|
|
|
|
validates Compose, starts a container, checks health/ready and restarts for persistence.
|
|
|
|
|
|
|
|
|
|
|
|
## Known Limitations / 已知边界
|
|
|
|
|
|
|
|
|
|
|
|
Cloud acknowledgements do not prove physical display pixels. Model fields may be
|
|
|
|
|
|
omitted and are explicitly marked configured. No native slideshow membership API,
|
|
|
|
|
|
no cloud upload-only API, no automatic retries after uncertain writes. Cache has no
|
|
|
|
|
|
automatic quota/GC in 0.1.0; monitor data volume. Rendering assets are local, limited
|
|
|
|
|
|
to 25M pixels/20 MiB. This release uses a single worker and is not multi-replica.
|
|
|
|
|
|
Gallery reads have no pagination because upstream documents none. Web UI hides
|
|
|
|
|
|
secrets and lazy-loads thumbnails; generating a first thumbnail fetches one original.
|
|
|
|
|
|
Multi-client provisioning UI/webhook delivery are not implemented. Docker build/NAS
|
|
|
|
|
|
status is recorded honestly in docs/ACCEPTANCE.md.
|
|
|
|
|
|
|
|
|
|
|
|
## Roadmap
|
|
|
|
|
|
|
|
|
|
|
|
Additional drivers; scoped token administration; signed webhooks; cache retention;
|
|
|
|
|
|
face-aware cropping; public weather provider; Google Calendar, Home Assistant, MQTT,
|
|
|
|
|
|
NAS monitoring and AI integrations through the same API. External integration is optional.
|
2026-10-02 08:43:07 +08:00
|
|
|
|
|
|
|
|
|
|
## Template editing
|
|
|
|
|
|
|
|
|
|
|
|
Templates provide bilingual form fields, event lists, local photo selection, advanced
|
|
|
|
|
|
JSON, color/grayscale previews, persistent defaults and reset to built-in defaults.
|
|
|
|
|
|
Changing content invalidates the previous preview. Weather uses the labeled Mock
|
|
|
|
|
|
provider until a real provider is configured. Live display controls stay disabled
|
|
|
|
|
|
until real writes are separately approved.
|
|
|
|
|
|
|
|
|
|
|
|
Browser smoke tests use a disposable Mock account via FRAME_FLOW_TEST_USERNAME
|
|
|
|
|
|
and FRAME_FLOW_TEST_PASSWORD; never run the mutation smoke suite against live devices.
|
2026-10-02 09:19:25 +08:00
|
|
|
|
|
|
|
|
|
|
## Current content and completed page workflows
|
|
|
|
|
|
|
|
|
|
|
|
Dashboard displays the actual current image and recorded content source. Frame Flow
|
|
|
|
|
|
stores immutable template/data/profile snapshots when it renders an asset and maps
|
|
|
|
|
|
successful upload results to the device/image ID. The current-content endpoint
|
|
|
|
|
|
matches the live device image ID; vendor images with no recorded source remain
|
|
|
|
|
|
explicitly unknown. Switching externally never makes the previous template appear
|
|
|
|
|
|
as the current one. Restore also restores the matching content provenance.
|
|
|
|
|
|
|
|
|
|
|
|
Templates can load the current recorded template or continue editing a generated
|
|
|
|
|
|
local image. They support direct photo import and protected image downloads.
|
|
|
|
|
|
Gallery provides search, sorting, dimensions and downloads. Display confirmation
|
|
|
|
|
|
supports duration, priority and restoration. History includes operation details.
|
|
|
|
|
|
Automation offers content pickers, template profiles, previews and manual execution;
|
|
|
|
|
|
playlists offer gallery image checkboxes. Real writes remain blocked by the server
|
|
|
|
|
|
regardless of the UI controls.
|
|
|
|
|
|
|
|
|
|
|
|
Settings persists weather coordinates/provider and image URL host allowlists in
|
|
|
|
|
|
SQLite. Open-Meteo provides current model weather and a three-day forecast, cached
|
|
|
|
|
|
for ten minutes on demand. Network failures show unavailable/stale data and use a
|
|
|
|
|
|
two-minute backoff, without replacing it with fictitious live data. Default remains
|
|
|
|
|
|
Mock until configured in Settings. Open-Meteo attribution is included in the UI and
|
|
|
|
|
|
weather/dashboard renders. Image URL import is HTTPS-only with explicit hostnames,
|
|
|
|
|
|
public-address validation and pinned DNS; redirects and private addresses are blocked.
|
|
|
|
|
|
|
|
|
|
|
|
Additional v1 endpoints: `/devices/{id}/current-content`, `/assets/{id}/source`,
|
|
|
|
|
|
`/providers/weather`, `/settings/weather`, `/settings/image-url`, `/jobs/{id}`,
|
|
|
|
|
|
`/jobs/{id}/preview`, `/jobs/{id}/run` (all under `/api/v1`). Existing contracts remain
|
|
|
|
|
|
compatible; new endpoints are included in generated OpenAPI and contract tests.
|