{"id":51086476,"url":"https://github.com/dlicudi/rustdecks","last_synced_at":"2026-06-23T22:04:34.931Z","repository":{"id":365572902,"uuid":"1271240702","full_name":"dlicudi/rustdecks","owner":"dlicudi","description":"A lean Loupedeck Live controller for X-Plane 12, in Rust — one small binary, one YAML profile per aircraft.","archived":false,"fork":false,"pushed_at":"2026-06-17T23:03:12.000Z","size":615,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T00:21:52.764Z","etag":null,"topics":["cockpit","flight-simulation","loupedeck","rust","streamdeck","x-plane","xplane"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dlicudi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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-06-16T13:21:12.000Z","updated_at":"2026-06-17T23:03:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dlicudi/rustdecks","commit_stats":null,"previous_names":["dlicudi/rustdecks"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/dlicudi/rustdecks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlicudi%2Frustdecks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlicudi%2Frustdecks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlicudi%2Frustdecks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlicudi%2Frustdecks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dlicudi","download_url":"https://codeload.github.com/dlicudi/rustdecks/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlicudi%2Frustdecks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34708310,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"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":["cockpit","flight-simulation","loupedeck","rust","streamdeck","x-plane","xplane"],"created_at":"2026-06-23T22:04:34.111Z","updated_at":"2026-06-23T22:04:34.926Z","avatar_url":"https://github.com/dlicudi.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rustdecks\n\nA lean [Loupedeck Live](https://loupedeck.com/) controller for [X-Plane 12](https://www.x-plane.com/),\nin Rust. A deliberately small alternative to [cockpitdecks](https://github.com/devleaks/cockpitdecks):\nLoupedeck Live only, no web decks, no async runtime — ~1,900 lines and a handful\nof dependencies.\n\n![A rustdecks deck page: 7-segment flight readouts, LED annunciators, accent-barred icon keys, encoder side strips, and the round nav LEDs](docs/preview.png)\n\n*Rendered by the bundled renderer — `cargo run --example hero`.*\n\n## Requirements\n\n- A Loupedeck Live (USB)\n- X-Plane **12.1.4+** with the Web API enabled (Settings → Network → *Accept connections… / Web API*)\n- Rust (stable)\n\n## Build \u0026 run\n\n```sh\ncargo build --release\n\n# Run a profile (this is the normal mode)\n./target/release/rustdecks examples/cessna-172.yaml\n\n# Component smoke tests\n./target/release/rustdecks probe                 # Loupedeck only: light LEDs, draw, print input\n./target/release/rustdecks simprobe [dataref]    # X-Plane only: subscribe to a dataref, print values\n```\n\nThe simulator host is discovered via the X-Plane UDP beacon; if no beacon is\nseen it falls back to `127.0.0.1`. Set `sim.host` in the profile to override.\n\nCargo aliases (in `.cargo/config.toml`) wrap the common commands — `cargo --list`\nto see them all:\n\n```sh\ncargo rd examples/cessna-172.yaml                 # run a profile (normal mode)\ncargo tui examples/cirrus-sr22.yaml               # terminal dashboard (virtual deck)\ncargo check-profile examples/cirrus-sr22.yaml     # validate a profile, no hardware/sim\ncargo probe                                       # Loupedeck-only smoke test\ncargo simprobe [dataref]                          # X-Plane-only smoke test\n```\n\n## Profile format\n\nA profile is one YAML file describing pages. Each page has up to 12 screen\n**keys** (`0..11`), 6 **encoders** (`e0..e5`, which draw to the side strips),\nand 8 round LED **buttons** (`b0..b7`). Every input may carry an `Action`; every\nsurface may carry a `Draw`.\n\n```yaml\ndevice: loupedeck-live\nbrightness: 0.7            # 0.0 .. 1.0\nsim:\n  host: auto              # \"auto\" = beacon discovery, or an IP/hostname\n  port: 8086\n\nhome: main                # page shown at startup\n\npages:\n  main:\n    keys:\n      0:\n        draw:                                       # what's shown\n          text: GEN                                 # static label\n          value: sim/cockpit2/electrical/generator_on[0]   # live dataref ([i] indexes arrays)\n          format: \"{:.0}\"                           # see \"Formatting\" below\n        press: { command: sim/electrical/generator_1_toggle }   # what a tap does\n      3:\n        draw: { text: \"RADIO →\" }\n        press: { page: radio }                      # switch pages\n\n    encoders:\n      e0:\n        draw: { text: THR, value: sim/flightmodel/engine/ENGN_thro[0], scale: 100.0, format: \"{:.0}%\" }\n        turn_cw:  { command: sim/engines/throttle_up }\n        turn_ccw: { command: sim/engines/throttle_down }\n        press:    { command: sim/engines/throttle_full }\n\n    leds:\n      b0:\n        color: \"#00ff00\"                            # #rrggbb, #rgb, or a name\n        press: { set-dataref: sim/cockpit2/switches/landing_lights_on, value: 1.0 }\n\n  radio:\n    keys:\n      0:\n        draw: { text: \"← BACK\" }\n        press: { page: main }\n```\n\n### Draw fields\n\n| field | meaning |\n|---|---|\n| `text` | static label (top of the cell) |\n| `value` | dataref path to display; `name[i]` indexes an array element |\n| `scale`, `offset` | `shown = value * scale + offset` (defaults 1, 0) |\n| `format` | format string applied to the number (default `\"{}\"`) |\n| `text_color`, `bg_color` | `#rrggbb`, `#rgb`, or a named color |\n| `lit_color` | makes the key an annunciator: a small LED bar glows this color when `value` ≥ 0.5 (dim when off), label below |\n| `icon` | FontAwesome glyph name (`engine`, `radio`, …) or hex codepoint; renders as an icon key |\n| `accent` | color of a thin bar across the top of the key (`#rrggbb`, `#rgb`, or a name) |\n| `font` | `seven-seg` renders the value in a 7-segment avionics face; otherwise the default monospace |\n\n### Actions\n\n- `{ command: \u003cname\u003e }` — fire an X-Plane command (momentary press+release)\n- `{ page: \u003cname\u003e }` — switch the deck to another page\n- `{ set-dataref: \u003cname\u003e, value: \u003cnum\u003e }` — write a dataref\n\nKeys take `press` / `long_press`; encoders take `turn_cw` / `turn_ccw` / `press`;\nLEDs take `color` / `press`.\n\n### Shared defaults\n\nTop-level `encoders` and `leds` are inherited by every page (a page may override\nan individual id). This is how the SR22 profile puts the FCU knobs and the 8-button\npager on every page without repetition.\n\n### Formatting\n\nA single `{...}` placeholder with optional precision, plus surrounding text:\n`\"{}\"`, `\"{:.0}\"`, `\"{:.1}\"`, `\"{:.0} ft\"`, `\"{:.0}%\"`. Anything without a\nplaceholder is printed verbatim (handy for fixed labels).\n\n## How it works\n\nFive modules, ~1,900 lines, no async:\n\n- **config** — the profile schema (serde) and validation\n- **device** — Loupedeck Live serial driver (WebSocket-framed binary over USB CDC)\n- **render** — an in-house RGB canvas + `fontdue`, packing to the device's RGB565\n- **sim** — X-Plane 12 Web API client (beacon discovery, REST id resolution, WebSocket values/commands)\n- **app** — the wiring loop\n\nThree threads — device read, sim WebSocket, main — communicate over channels.\nThe main thread coalesces update bursts and repaints a surface only when its\ndisplayed *text* actually changes.\n\n## Not included (by design)\n\nSerial pinning, command hold (begin/end), dataref-driven LED colors, side-strip\ntouch, multi-value keys, an equality test for annunciators (`light when value ==\nN`, needed for mode selectors), and any formula/expression engine. These were cut\nto stay lean; they can be added when a real profile needs them.\n\n## Credits \u0026 license\n\nrustdecks stands entirely on the work of **Pierre M. ([devleaks](https://github.com/devleaks))**:\n\n- [**python-loupedeck-live**](https://github.com/devleaks/python-loupedeck-live) —\n  the reverse-engineered Loupedeck Live serial protocol that rustdecks' device\n  layer is a Rust port of. Without it there'd be nothing to talk to the hardware.\n- [**cockpitdecks**](https://github.com/devleaks/cockpitdecks) — the X-Plane\n  controller that defined this whole idea: the page/encoder/LED model, the dataref\n  and command vocabulary, and the icon/font choices. rustdecks is a deliberately\n  lean reimplementation of a slice of cockpitdecks; for the full-featured,\n  multi-deck, web-capable original, use cockpitdecks.\n\nThe shipped SR22 ([`examples/cirrus-sr22.yaml`](examples/cirrus-sr22.yaml)) and\nCessna 172 SP ([`examples/cessna-172.yaml`](examples/cessna-172.yaml)) profiles are\nported from my own cockpitdecks deck configs at\n[dlicudi/cockpitdecks-configs](https://github.com/dlicudi/cockpitdecks-configs).\n([`examples/demo.yaml`](examples/demo.yaml) is a minimal vocabulary demo.)\n\nCode is MIT (see [LICENSE](LICENSE)); upstream MIT copyright notices for the\nported protocol and SR22 profile are preserved in\n[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md). Bundled fonts, each under the\nSIL Open Font License with its license alongside in [assets/fonts/](assets/fonts/):\n\n- [B612](https://github.com/polarsys/b612) — labels/values, designed for cockpit displays.\n- [Segment7](https://fontlibrary.org/en/font/segment7) by Cedric Knight — the 7-segment avionics readout face.\n- [Font Awesome 6 Free (Solid)](https://fontawesome.com/) — nav/menu icon glyphs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdlicudi%2Frustdecks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdlicudi%2Frustdecks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdlicudi%2Frustdecks/lists"}