Build Pigway Frame Flow with bilingual UI, complete KoKonna driver and safe automation
This commit is contained in:
+115
@@ -0,0 +1,115 @@
|
||||
# Synology NAS deployment / 群晖部署
|
||||
|
||||
Target: DS923+, DSM 7.3.1, AMD64, Container Manager.
|
||||
Directory: /volume1/docker/pigway-frame-flow. Port: 18787.
|
||||
|
||||
## 1. Package and upload / 打包上传
|
||||
|
||||
From the repository:
|
||||
|
||||
```sh
|
||||
python3 scripts/package-nas.py
|
||||
scp -O dist/pigway-frame-flow-nas.tar.gz nas-example:/tmp/pigway-frame-flow-nas.tar.gz
|
||||
ssh nas-example
|
||||
```
|
||||
|
||||
The package contains tracked source/config examples/scripts only, never .env,
|
||||
actual keys, data or .git. It is a deployment artifact, not a Git backup.
|
||||
Use `git bundle create frame-flow.bundle --all` separately for history backups.
|
||||
|
||||
## 2. Prepare project directory / 准备目录
|
||||
|
||||
Verify target is empty or explicitly backed up before extracting. Do not extract
|
||||
onto another existing installation or other services. If the directory exists,
|
||||
inspect it first; preserve its .env and data.
|
||||
|
||||
```sh
|
||||
mkdir -p /volume1/docker/pigway-frame-flow
|
||||
# Only for an empty/new target:
|
||||
tar -xzf /tmp/pigway-frame-flow-nas.tar.gz -C /volume1/docker
|
||||
cd /volume1/docker/pigway-frame-flow
|
||||
mkdir -p data/images data/generated data/cache config
|
||||
sudo chown -R 10333:10333 data config
|
||||
cp .env.example .env
|
||||
chmod 600 .env
|
||||
```
|
||||
|
||||
Edit `.env` on the NAS. Set FRAME_FLOW_TOKEN to a strong random token. API key
|
||||
stays empty. KOKONNA_MOCK=true and KOKONNA_WRITE_ENABLED=false.
|
||||
Set WEB_BIND to the actual NAS LAN address (recommended) or explicitly 0.0.0.0;
|
||||
WEB_PORT=18787. The latter binds every NAS interface, so use the LAN IP when
|
||||
strict LAN-only binding is required. No firewall/router/proxy edits are performed.
|
||||
Copy deploy/devices.yml.example to config/devices.yml if multiple devices are desired.
|
||||
|
||||
## 3. Validate, build, start / 验证构建启动
|
||||
|
||||
Synology Docker is /usr/local/bin/docker. The SSH user may require sudo. Do not
|
||||
change socket ownership, groups, Docker network settings or other containers.
|
||||
|
||||
```sh
|
||||
sudo /usr/local/bin/docker compose config --quiet
|
||||
sudo /usr/local/bin/docker compose build --pull
|
||||
sudo /usr/local/bin/docker compose up -d
|
||||
sudo /usr/local/bin/docker compose ps
|
||||
sudo /usr/local/bin/docker compose logs --tail 100
|
||||
sudo /usr/local/bin/docker inspect pigway-frame-flow \
|
||||
--format 'id={{.Id}} started={{.State.StartedAt}} health={{.State.Health.Status}} restart={{.HostConfig.RestartPolicy.Name}} ports={{json .NetworkSettings.Ports}} mounts={{json .Mounts}} network={{json .NetworkSettings.Networks}}'
|
||||
sudo /usr/local/bin/docker image inspect pigway-frame-flow:latest \
|
||||
--format 'id={{.Id}} size={{.Size}} architecture={{.Architecture}}'
|
||||
```
|
||||
|
||||
Do not print unfiltered `docker inspect`: its environment can contain keys/tokens.
|
||||
Likewise do not print `docker compose config` without --quiet on a live installation.
|
||||
Run the read-only smoke checker from a machine with Python and set FRAME_FLOW_URL
|
||||
to http://NAS_LAN_IP:18787; FRAME_FLOW_TOKEN stays in private env/file.
|
||||
|
||||
Health: GET /health → 200. Readiness: GET /ready → 200 when SQLite, driver config,
|
||||
auth and Scheduler are ready. Missing credentials in live mode → 503. The cloud
|
||||
is not probed by readiness. Cloud loss does not cause healthcheck restart loops.
|
||||
|
||||
## 4. Verify persistence / 持久化
|
||||
|
||||
Create a disabled test schedule in Web UI and locally stage one image (Mock only).
|
||||
Record the job and asset IDs, then:
|
||||
|
||||
```sh
|
||||
sudo /usr/local/bin/docker compose restart pigway-frame-flow
|
||||
```
|
||||
|
||||
Wait for healthy state, check /health and /ready, and verify the same job/asset
|
||||
IDs and history remain. Database is data/frameflow.db with WAL/SHM alongside it;
|
||||
back up while stopped or use SQLite backup API, not a lone live DB file copy.
|
||||
Restart policy must read unless-stopped. Do not reboot the NAS for acceptance.
|
||||
|
||||
## 5. Live read-only verification / 实机只读
|
||||
|
||||
After Mock acceptance, fill KOKONNA_API_KEY; set mock=false, keep writes=false.
|
||||
Recreate only this service to apply env. Query device and images metadata only.
|
||||
Verify known model/geometry/timezone/name against real data; missing vendor model
|
||||
fields are not claimed verified. No original downloads are necessary. Stop on
|
||||
mismatch; never try to fix cloud state automatically. Obtain explicit owner
|
||||
permission before any upload/switch/delete. See REAL_DEVICE_TEST.md.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
/volume1/docker/pigway-frame-flow/
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
.env
|
||||
requirements.lock
|
||||
pigway_frame_flow/
|
||||
vendor/
|
||||
scripts/
|
||||
config/
|
||||
data/
|
||||
frameflow.db
|
||||
images/
|
||||
generated/
|
||||
cache/
|
||||
```
|
||||
|
||||
Container runs UID/GID 10333:10333; only data/config need ownership. Avoid recursive
|
||||
ownership changes outside this project's paths. Standard Compose bridge is private
|
||||
and isolated by project name. Logs use stdout/stderr with Docker's capped json-file.
|
||||
No logs directory or Docker socket mount.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Real device test plan / 单次实机测试计划
|
||||
|
||||
**Not executed. Explicit owner authorization is required before any write.**
|
||||
只读核对完成后询问:“真实设备只读验收完成,是否允许进行一次真实图片上传与显示测试?”
|
||||
|
||||
1. GET Frame Flow device (driver POST /device); record current imageId AND coverId,
|
||||
synced, geometry, model, rotation, timezone, firmware, heartbeat and battery.
|
||||
2. POST /listImages for metadata only. Record count, current image, sorted IDs,
|
||||
duplicates, name/date/dimensions anomalies. No download-all action.
|
||||
3. Stop on discrepancy. Missing model fields must be reported as unavailable.
|
||||
4. Once explicitly authorized, locally render **one** 1200×1600 test image containing
|
||||
PIGWAY FRAME FLOW TEST and Shanghai timestamp, no private content.
|
||||
5. Record an operation ID and previous image; one upload only, no write retries.
|
||||
6. Poll device read-only no more often than 60 seconds, respecting the 20/min budget.
|
||||
Confirm imageId and synced. If sent but response lost, mark UNKNOWN, block writes,
|
||||
reconcile gallery/device; never upload again to guess the outcome.
|
||||
7. Keep the test image visible at most 1–3 minutes, then restore the recorded artwork
|
||||
with a separate tracked switch operation. Query once more to confirm restored
|
||||
imageId and synced. Record coverId after restoration too.
|
||||
8. Delete only that known test image after restoration is positively confirmed AND
|
||||
it is not a base/previous/override/current reference. If ambiguous, leave it and
|
||||
report. Never delete art or retry a timed-out delete.
|
||||
9. Disable real writes again. Report each operation ID/result and remaining test image.
|
||||
|
||||
The API cannot set coverId. Restoration guarantees a requested image switch when
|
||||
confirmed, not arbitrary cover metadata restoration. Physical screen refresh has
|
||||
no documented pixel telemetry; visual owner observation supplements cloud synced.
|
||||
@@ -0,0 +1,55 @@
|
||||
# KoKonna API research / 接口调查
|
||||
|
||||
Checked 2026-10-01 against https://kokonna.art/pages/openapi.
|
||||
Base: https://api.kokonna.art/openapi. No fallback to legacy galaxyguide.cn.
|
||||
|
||||
| Official endpoint | Frame Flow capability |
|
||||
|---|---|
|
||||
| POST /device | Device panel, standardized device model, charging, firmware, sync, cover, storage, native slideshow mode/interval |
|
||||
| POST /upload | Rendered JPEG upload and immediate activation, asynchronous operation |
|
||||
| POST /listImages | On-demand cloud metadata gallery, current marker, name/date/geometry/size |
|
||||
| POST /deleteImage | Confirmed deletion with restore-reference protection |
|
||||
| GET /image/:apikey/:imageId | Authenticated server proxy, original bytes, lazy cached thumbnails; key never sent to browser |
|
||||
| POST /displayImageById | Switch through unified DisplayService |
|
||||
| POST /displayImageByName | Explicit filename switch; ambiguous names rejected |
|
||||
|
||||
## Native slideshow / 原生轮播
|
||||
|
||||
`switchType` (queue/random) and `switchMinute` are readable through device.
|
||||
There is no documented playlist-membership API and no slideshow-setting API.
|
||||
`listImages` returns the entire cloud gallery sorted by descending ID; it does
|
||||
not establish playlist membership. No official pagination parameter is documented.
|
||||
Frame Flow labels membership as unavailable, native editing unsupported, and
|
||||
provides its own SQLite-backed playlists as an independent capability.
|
||||
|
||||
## Cloud constraints / 云端边界
|
||||
|
||||
Official limit: 20 requests per minute per device key; local transport reserves
|
||||
headroom at 18. Device state has a 60-second cache, gallery is loaded on demand.
|
||||
Uploads automatically activate a new image; there is no upload-without-activation
|
||||
flag. Gallery-only uploads are therefore local staging, not a fictitious cloud
|
||||
operation. Maximum upstream request size is 50 MB; this service accepts 20 MiB
|
||||
image files with bounded decoded pixels. No non-idempotent cloud write is retried.
|
||||
Cloud acknowledgement is not physical refresh confirmation; /device synced and
|
||||
imageId drive the operation state. Unconfirmed or interrupted writes become UNKNOWN
|
||||
and block later device writes until read-only reconciliation/explicit acknowledgement.
|
||||
|
||||
## Reuse / 复用
|
||||
|
||||
ClawHub API: https://clawhub.ai/api/v1/skills/kokonna-frame
|
||||
Versioned ZIP: https://clawhub.ai/api/v1/download?slug=kokonna-frame&version=1.2.0
|
||||
Community package owner: guooscar; metadata license MIT-0. Reused protocol payload
|
||||
and device request, with an injected safe transport. It is a skill/CLI, not a
|
||||
proven NAS daemon, and its default host and screen field assumptions are dated.
|
||||
We implement scheduling, storage, driver normalization, renderer, REST, auth,
|
||||
operations, idempotency, recovery, bilingual UI and deployment independently.
|
||||
|
||||
Other inspected repositories:
|
||||
- https://github.com/patrickchen914/kokonna : Home Assistant integration, MIT;
|
||||
depends on Home Assistant and had legacy host/key-in-image-URL behavior.
|
||||
- https://github.com/nixihz/kokonna-skill : broader CLI/Hermes endpoint coverage,
|
||||
README MIT, legacy host and raw upstream error messages. Not incorporated.
|
||||
- https://github.com/zhengweiran66-eng/kokonna-product.html : product page.
|
||||
|
||||
No real device key was used during development. Cloud host root returned 530 in
|
||||
an unauthenticated research request; no claim of current real-device connectivity.
|
||||
@@ -0,0 +1,101 @@
|
||||
# Optional external integration contract
|
||||
|
||||
Ticktar integration is optional. Frame Flow has no runtime dependency on Ticktar.
|
||||
No external client receives a KoKonna API key, accesses the SQLite database, or
|
||||
imports a vendor SDK. Web UI and external clients use the same application services.
|
||||
|
||||
## Architecture
|
||||
|
||||
External client → /api/v1 → Application / DisplayService → FrameDeviceDriver → KoKonna.
|
||||
Configure the caller with FRAME_FLOW_URL and FRAME_FLOW_TOKEN only. No caller-specific
|
||||
branch exists in core. Frame Flow starts and works with no integration configuration.
|
||||
|
||||
## Authentication and permissions
|
||||
|
||||
Authorization: Bearer FRAME_FLOW_TOKEN. The bootstrap token is provided through
|
||||
.env or a secret file; database stores SHA-256 hash only. The client model supports
|
||||
client_id, name, permissions, enabled, created_at, last_used_at. Permissions:
|
||||
devices:read, images:read, images:delete, display:write, templates:read, render:write,
|
||||
jobs:read, jobs:write, history:read, settings:write, admin.
|
||||
0.1.0 seeds one admin token; provisioning scoped client rows is supported by middleware
|
||||
but multi-token management UI is not implemented. Use a trusted server-to-server
|
||||
connection; CORS is opt-in by exact origins, never wildcard by default.
|
||||
|
||||
## Discovery and capabilities
|
||||
|
||||
GET /api/v1/devices; GET /api/v1/devices/{id}; GET /api/v1/templates.
|
||||
Device provides id/name/status, display geometry, power, current_content, firmware,
|
||||
model/model_code, timezone, last_seen_at, capabilities. Model fallback is flagged
|
||||
model_verified=false when the cloud omits model fields. Do not branch on driver:
|
||||
check capabilities upload/gallery/delete/switch/battery/color/remote_refresh.
|
||||
Native slideshow members/editing are unavailable in KoKonna's documented API.
|
||||
|
||||
## Display
|
||||
|
||||
POST /api/v1/display, Idempotency-Key: a-unique-client-request-key
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "living-room",
|
||||
"content": {
|
||||
"type": "template",
|
||||
"template": "daily-dashboard",
|
||||
"data": {"title": "Good Morning", "events": []},
|
||||
"profile": "color"
|
||||
},
|
||||
"options": {"duration": 1800, "restore_previous": true, "priority": 50},
|
||||
"source": {"client": "external-app", "request_id": "caller-trace"}
|
||||
}
|
||||
```
|
||||
|
||||
Content supports image (exactly one image_id/image_name/asset_id/base64), image_url
|
||||
(HTTPS/public DNS/exact allowlist), template. html/markdown explicitly return
|
||||
UNSUPPORTED_CAPABILITY. For upload-only use device /upload with display_now=false;
|
||||
this is safe local staging because KoKonna cloud upload always activates an image.
|
||||
|
||||
Returns HTTP 202: {operation_id, job_id, status:"accepted"}. job_id is a legacy
|
||||
acceptance alias for operation_id, not a scheduler job ID.
|
||||
|
||||
## Render and operations
|
||||
|
||||
POST /api/v1/render with template content returns 202 and a local rendered asset
|
||||
when completed; no device write. GET /api/v1/operations/{operation_id}:
|
||||
QUEUED → RENDERING → UPLOADING → SYNCING → COMPLETED / FAILED / UNKNOWN.
|
||||
Result includes image_id or asset_id/url. SYNCING confirms imageId plus synced
|
||||
through read-only device status; there is no documented physical-screen telemetry.
|
||||
Token must own the operation or be admin. Never interpret HTTP 202 as refreshed.
|
||||
|
||||
## Idempotency
|
||||
|
||||
Key is scoped to authenticated client, retained seven days. Same key and same
|
||||
normalized action/body returns the same operation, even across restart. A different
|
||||
body with the same key returns CONFLICT (409). Do not retry UNKNOWN with a new key.
|
||||
Cloud writes have no automatic retries. UNKNOWN blocks further device writes;
|
||||
admin /operations/{id}/reconcile reads device/gallery and may acknowledge a manually
|
||||
verified outcome without reissuing a write.
|
||||
|
||||
## Errors and tracing
|
||||
|
||||
All API errors: {error:{code,message,request_id,details:{}}}. Stable codes include
|
||||
INVALID_REQUEST, UNAUTHORIZED, FORBIDDEN, DEVICE_NOT_FOUND, DEVICE_OFFLINE,
|
||||
UNSUPPORTED_CAPABILITY, RENDER_FAILED, UPLOAD_FAILED, WRITE_RESULT_UNKNOWN,
|
||||
RATE_LIMITED, INTERNAL_ERROR, CONFIG_REQUIRED, WRITE_DISABLED, CONFLICT, NOT_FOUND,
|
||||
CLOUD_UNAVAILABLE. Use code, not message text. Validation does not echo inputs.
|
||||
X-Request-ID accepts a bounded safe identifier; it is returned on the response.
|
||||
The authenticated client is authoritative; source.client is optional metadata.
|
||||
History stores client_id/request_id/operation_id/device_id/action/result/timestamp.
|
||||
No internal endpoints, headers, stacks, paths or secrets are returned.
|
||||
|
||||
## Future webhook
|
||||
|
||||
SQLite events and webhook_subscriptions reserve operation.completed/failed,
|
||||
display.changed, device.online/offline/low_battery events. 0.1.0 emits operation/display
|
||||
events only; no callback delivery, retry worker or registration API is implemented.
|
||||
Future deliveries should use signed payloads and bounded retries.
|
||||
OAuth, tenancy, RBAC management and generated SDKs are future work.
|
||||
|
||||
## Compatibility
|
||||
|
||||
/openapi.json and /docs are the contract. Snapshot tests cover paths, response
|
||||
codes, standard device fields, security and operation enums. Static snapshot is
|
||||
also supplied in docs/openapi.json. Breaking changes require /api/v2.
|
||||
+6766
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user