{"id":49338135,"url":"https://github.com/zentala/ulanzi-deck-d200-plugin-example","last_synced_at":"2026-04-27T02:03:14.181Z","repository":{"id":354051374,"uuid":"1221615563","full_name":"zentala/ulanzi-deck-d200-plugin-example","owner":"zentala","description":"Three working examples of controlling the Ulanzi Deck D200 — official Plugin SDK (Node.js), direct USB (Python/strmdck), and ADB framebuffer. Plus a complete research write-up.","archived":false,"fork":false,"pushed_at":"2026-04-26T23:25:55.000Z","size":288,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T23:26:55.907Z","etag":null,"topics":["adb-framebuffer","nodejs","plugin-example","python","rockchip","stream-deck","strmdck","ulanzi-d200","ulanzideck","ulanzistudio"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zentala.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-26T13:07:04.000Z","updated_at":"2026-04-26T23:26:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zentala/ulanzi-deck-d200-plugin-example","commit_stats":null,"previous_names":["zentala/ulanzi-deck-d200-plugin-example"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/zentala/ulanzi-deck-d200-plugin-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zentala%2Fulanzi-deck-d200-plugin-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zentala%2Fulanzi-deck-d200-plugin-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zentala%2Fulanzi-deck-d200-plugin-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zentala%2Fulanzi-deck-d200-plugin-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zentala","download_url":"https://codeload.github.com/zentala/ulanzi-deck-d200-plugin-example/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zentala%2Fulanzi-deck-d200-plugin-example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32319560,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"online","status_checked_at":"2026-04-27T02:00:06.769Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adb-framebuffer","nodejs","plugin-example","python","rockchip","stream-deck","strmdck","ulanzi-d200","ulanzideck","ulanzistudio"],"created_at":"2026-04-27T02:03:09.261Z","updated_at":"2026-04-27T02:03:14.170Z","avatar_url":"https://github.com/zentala.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ulanzi-deck-d200-plugin-example\n\n[![CI](https://github.com/zentala/ulanzi-deck-d200-plugin-example/actions/workflows/ci.yml/badge.svg)](https://github.com/zentala/ulanzi-deck-d200-plugin-example/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nThree working examples of how to control the **Ulanzi Deck D200** smart key display, plus a complete research write-up of the device.\n\n\u003e **Just want to get started?** Most users want **Approach A — Plugin SDK** (skip to [its README](ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/README.md)). Approach B is for users who want to skip UlanziStudio.\n\n\u003e **Disclaimer**: this is a personal, unofficial example repo. Not affiliated with or endorsed by Ulanzi. The official SDK lives at [UlanziTechnology](https://github.com/UlanziTechnology) and is included here as git submodules.\n\n## What's the D200?\n\nA USB-connected stream-deck-style controller with 6 LCD buttons (72×72 px each) and a main 480×272 LCD. It runs a Linux Buildroot userspace on a Rockchip RK3308HS SoC. Three independent ways exist to drive it — this repo demonstrates all three.\n\n## Three approaches\n\n| | **A. Plugin SDK** | **B1. USB / strmdck** | **B2. ADB framebuffer** |\n|---|---|---|---|\n| Official app required | ✅ UlanziStudio | ❌ | ❌ |\n| Language | Node.js / browser JS | Python | Python + ADB shell |\n| Driver | UlanziStudio handles USB | libusb (Zadig on Windows) | ADB |\n| Granularity | Per-button actions | Per-button images | Whole-screen framebuffer |\n| Native FPS | Real-time | ~2–10 fps | ~10–15 fps |\n| Use when | You want plugin-style buttons integrated into Ulanzi's UI | You want to drive the buttons without installing UlanziStudio | You want full pixel control over the main LCD |\n| Demo location | [`ulanzi-demos/plugins/demo/`](ulanzi-demos/plugins/demo/) | [`ulanzi-demos/usb/demo/`](ulanzi-demos/usb/demo/) | [`ulanzi-demos/shell/demo/`](ulanzi-demos/shell/demo/) |\n| Spec | [`SPEC-A-plugin-sdk.md`](ulanzi-demos/SPEC-A-plugin-sdk.md) | [`SPEC-B-direct-access.md`](ulanzi-demos/SPEC-B-direct-access.md) | [`SPEC-B-direct-access.md`](ulanzi-demos/SPEC-B-direct-access.md) |\n\nFor the deep dive on architecture, hardware internals, and full SDK API surface see [`docs/ULANZI-SDK-RESEARCH.md`](docs/ULANZI-SDK-RESEARCH.md).\n\n## Approach A — Plugin SDK demo (6 actions)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/assets/previews/clock.png\" width=\"110\" alt=\"Clock\" /\u003e\n  \u003cimg src=\"ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/assets/previews/counter.png\" width=\"110\" alt=\"Counter\" /\u003e\n  \u003cimg src=\"ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/assets/previews/status.png\" width=\"110\" alt=\"CPU Status\" /\u003e\n  \u003cimg src=\"ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/assets/previews/calendar.png\" width=\"110\" alt=\"Calendar\" /\u003e\n  \u003cimg src=\"ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/assets/previews/pomodoro-work.png\" width=\"110\" alt=\"Pomodoro\" /\u003e\n  \u003cimg src=\"ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/assets/previews/weather-clear.png\" width=\"110\" alt=\"Weather\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003csub\u003eClock · Counter · CPU Status · Calendar · Pomodoro · Weather — all rendered live on the device's 196×196 button. \u003ca href=\"ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/README.md#action-previews\"\u003eFull preview gallery →\u003c/a\u003e\u003c/sub\u003e\u003c/p\u003e\n\nA reference plugin demonstrating canvas rendering, settings persistence, lifecycle events, Property Inspector wiring, and HTTP fetch across six independent actions:\n\n| Action | What it does |\n|---|---|\n| **Clock** | Digital HH:MM clock with animated seconds-progress border. Tap toggles between two configurable IANA timezones (defaults: Europe/Warsaw and UTC). |\n| **Counter** | Tap-to-count with configurable step, direction, and colors. Settings persist. |\n| **CPU Status** | CPU load + temperature monitor with EMA smoothing. Reads [LibreHardwareMonitor](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor) HTTP API; falls back to a Web Worker timing benchmark. Configurable overheat threshold + alert. |\n| **Calendar** | Torn-off calendar — day, month name, year, day-of-week. Refreshes at midnight. Tap opens a configurable calendar URL (Google / Outlook / iCloud / Proton). |\n| **Pomodoro** | Work/break timer state machine. Configurable durations; auto-pauses when the view is hidden. |\n| **Weather** | Current temperature + condition from [open-meteo.com](https://open-meteo.com) (no API key). Configurable location (lat/lon), label, units, refresh interval. |\n\nStack: plain JS (no module system, browser-style globals), Jest tests in a `vm` sandbox, ESLint + Prettier + Husky pre-commit. UUIDs centralised in [`plugin/uuids.js`](ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/plugin/uuids.js). 126 unit + dispatcher + manifest tests. Preview PNGs are deterministic (fixed clock 09:41, mocked fetch) and auto-regenerated by the pre-commit hook whenever an action changes.\n\nSee [the plugin's own README](ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin/README.md) for installation (symlink instructions per OS).\n\n## Approach B1 — Python USB dashboard\n\nLive dashboard rendered onto 4 physical D200 buttons, no UlanziStudio:\n\n- Slot 0: clock HH:MM\n- Slot 1: CPU% (color-coded green/yellow/red by threshold)\n- Slot 2: RAM% (same color scheme)\n- Slot 3: static \"DEMO\" label\n\nStack: Python 3.9+, [redphx/strmdck](https://github.com/redphx/strmdck) for USB transport, Pillow for image rendering, psutil for system metrics. Hatchling build, pytest with 80% coverage gate. Windows users need to install the WinUSB driver via Zadig (instructions in the demo README).\n\nSee [the demo's README](ulanzi-demos/usb/demo/README.md) for setup and `--fps` tuning.\n\n## Approach B2 — ADB framebuffer\n\nWrites raw pixels straight to `/dev/fb0` over `adb shell dd`. Three modes:\n- `--stream` — real-time pulse animation\n- `--generate` — pre-render N PNG frames to disk\n- `--play` — push pre-rendered frames to the device\n\nAuto-detects framebuffer geometry from `/sys/class/graphics/fb0/{virtual_size,bits_per_pixel,stride}`, supports both RGB565 and RGBA8888 with a numpy fast-path. Practical limit ~10–15 fps over USB 2.0 + ADB protocol overhead.\n\n\u003e **Note**: relies on the open ADB root shell that ships on the D200 by default. Future firmware updates may close it.\n\nSee [the demo's README](ulanzi-demos/shell/demo/README.md) for ADB setup and diagnostics.\n\n## Quick start\n\n```bash\ngit clone --recurse-submodules git@github.com:zentala/ulanzi-deck-d200-plugin-example.git\ncd ulanzi-deck-d200-plugin-example\n\n# Approach A — Plugin SDK\ncd ulanzi-demos/plugins/demo/io.zentala.ulanzideck.demo.ulanziPlugin\npnpm install\npnpm test\n\n# Approach B1 — USB dashboard\ncd ../../../usb/demo\npython -m venv .venv \u0026\u0026 source .venv/bin/activate   # or .venv\\Scripts\\Activate.ps1 on Windows\npip install -e . \u0026\u0026 pip install pytest pytest-cov\npytest\n\n# Approach B2 — ADB framebuffer\ncd ../../shell/demo\npip install -r requirements.txt\n./push.sh 10\n```\n\nIf you forgot `--recurse-submodules`:\n\n```bash\ngit submodule update --init --recursive\n```\n\n## Hardware requirements\n\n- **Ulanzi Deck D200** smart key display (480×272 LCD + 6 LCD buttons)\n- USB-C cable\n- For Approach A: Windows 10+ or macOS 10.11+ with UlanziStudio 6.1+ installed\n- For Approach B1: any OS with libusb access; Windows needs Zadig once\n- For Approach B2: any OS with `adb` installed\n\n## Status\n\n| Demo | Tests | Build | Linting |\n|---|---|---|---|\n| Plugin SDK | 123 Jest tests, dispatcher + 6 actions | n/a | ESLint + Prettier + Husky |\n| USB | pytest with 80% coverage gate | hatchling | ruff |\n| ADB | manual smoke (`push.sh`) | n/a | n/a |\n\nAll three demos verified running on a real D200.\n\n## Contributing\n\nPRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Issue reports for setup friction or unclear docs especially appreciated.\n\n## License\n\n[MIT](LICENSE) — use freely, keep the copyright notice.\n\nThe bundled SDK submodules under `libs/` are licensed by [UlanziTechnology](https://github.com/UlanziTechnology) under their own terms (AGPL 3.0 at last check — verify in the upstream repos).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzentala%2Fulanzi-deck-d200-plugin-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzentala%2Fulanzi-deck-d200-plugin-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzentala%2Fulanzi-deck-d200-plugin-example/lists"}