Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ AGENTS.override.md
# Machine-specific configuration
/config/joydex.json
/config/config.json
/firmware/esphome/secrets.yaml
/firmware/esphome/.esphome/
/firmware/esphome/.venv/

# Local reference material retained outside the public source set
/Virpil Mongoose T-50CM2+3 Fillable.pdf
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

This file records the major capabilities and fixes added since Joydex was first uploaded to GitHub. Entries are grouped by date because the project has not used versioned releases yet. New entries go at the top.

## 2026-07-27

- Added an experimental direct ESPHome touchscreen example for the
ESP32-4848S040C_I, including neutral and bridge-console skins, live task
state, four task controls, PLAN MODE, authenticated REST/SSE transport,
DPAPI-protected host configuration, physical-device documentation, and
recovery guidance.

## 2026-07-24

- Kept the physical task monitor focused on real Codex sidebar tasks. The [hook relay](src/Joydex.HookRelay/Program.cs) now ignores delegated agents identified by `agent_id` and internal ephemeral sessions that have no persistent `transcript_path`.
Expand Down
21 changes: 21 additions & 0 deletions Joydex.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E2410E01-290
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.Core", "src\Joydex.Core\Joydex.Core.csproj", "{3164D466-1B0D-496E-A00C-FCED33739C9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.WirelessPanel", "src\Joydex.WirelessPanel\Joydex.WirelessPanel.csproj", "{7E364531-9197-4856-A674-1F0E3F926E02}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.Windows", "src\Joydex.Windows\Joydex.Windows.csproj", "{828482D5-6A56-4401-8FF3-7EE416F246A0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.App", "src\Joydex.App\Joydex.App.csproj", "{18911FBD-4F2A-404A-A60C-F6CC51C9A626}"
Expand All @@ -19,10 +21,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{DEFBDF7A
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.Trace", "tools\Joydex.Trace\Joydex.Trace.csproj", "{DA7922AD-9900-4E7B-8479-190C57ED72DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.WirelessPanel.Configure", "tools\Joydex.WirelessPanel.Configure\Joydex.WirelessPanel.Configure.csproj", "{1E88D793-7289-42AB-ADDC-358E25BC5E5A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{450F8F5B-BB91-4471-AB6C-5FDD608C58C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.Tests", "tests\Joydex.Tests\Joydex.Tests.csproj", "{3B99BEE9-29C8-4704-9873-3D2E6D521BC3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Joydex.WirelessPanel.Tests", "tests\Joydex.WirelessPanel.Tests\Joydex.WirelessPanel.Tests.csproj", "{1028506F-A877-4F06-80C8-BD8BE73D71D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -36,6 +42,10 @@ Global
{3164D466-1B0D-496E-A00C-FCED33739C9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3164D466-1B0D-496E-A00C-FCED33739C9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3164D466-1B0D-496E-A00C-FCED33739C9D}.Release|Any CPU.Build.0 = Release|Any CPU
{7E364531-9197-4856-A674-1F0E3F926E02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E364531-9197-4856-A674-1F0E3F926E02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E364531-9197-4856-A674-1F0E3F926E02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E364531-9197-4856-A674-1F0E3F926E02}.Release|Any CPU.Build.0 = Release|Any CPU
{828482D5-6A56-4401-8FF3-7EE416F246A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{828482D5-6A56-4401-8FF3-7EE416F246A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{828482D5-6A56-4401-8FF3-7EE416F246A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -56,18 +66,29 @@ Global
{DA7922AD-9900-4E7B-8479-190C57ED72DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA7922AD-9900-4E7B-8479-190C57ED72DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA7922AD-9900-4E7B-8479-190C57ED72DD}.Release|Any CPU.Build.0 = Release|Any CPU
{1E88D793-7289-42AB-ADDC-358E25BC5E5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E88D793-7289-42AB-ADDC-358E25BC5E5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E88D793-7289-42AB-ADDC-358E25BC5E5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E88D793-7289-42AB-ADDC-358E25BC5E5A}.Release|Any CPU.Build.0 = Release|Any CPU
{3B99BEE9-29C8-4704-9873-3D2E6D521BC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B99BEE9-29C8-4704-9873-3D2E6D521BC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B99BEE9-29C8-4704-9873-3D2E6D521BC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B99BEE9-29C8-4704-9873-3D2E6D521BC3}.Release|Any CPU.Build.0 = Release|Any CPU
{1028506F-A877-4F06-80C8-BD8BE73D71D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1028506F-A877-4F06-80C8-BD8BE73D71D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1028506F-A877-4F06-80C8-BD8BE73D71D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1028506F-A877-4F06-80C8-BD8BE73D71D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3164D466-1B0D-496E-A00C-FCED33739C9D} = {E2410E01-2903-45FB-98C8-91DD1E194599}
{7E364531-9197-4856-A674-1F0E3F926E02} = {E2410E01-2903-45FB-98C8-91DD1E194599}
{828482D5-6A56-4401-8FF3-7EE416F246A0} = {E2410E01-2903-45FB-98C8-91DD1E194599}
{18911FBD-4F2A-404A-A60C-F6CC51C9A626} = {E2410E01-2903-45FB-98C8-91DD1E194599}
{5F62D9E7-D5D8-4E02-85FB-892A5E8C4A14} = {E2410E01-2903-45FB-98C8-91DD1E194599}
{6A7A958D-2D87-4E96-A2C4-214B593AE4C2} = {E2410E01-2903-45FB-98C8-91DD1E194599}
{DA7922AD-9900-4E7B-8479-190C57ED72DD} = {DEFBDF7A-8DC4-4D45-95B1-C55E7DDB342F}
{1E88D793-7289-42AB-ADDC-358E25BC5E5A} = {DEFBDF7A-8DC4-4D45-95B1-C55E7DDB342F}
{3B99BEE9-29C8-4704-9873-3D2E6D521BC3} = {450F8F5B-BB91-4471-AB6C-5FDD608C58C2}
{1028506F-A877-4F06-80C8-BD8BE73D71D4} = {450F8F5B-BB91-4471-AB6C-5FDD608C58C2}
EndGlobalSection
EndGlobal
39 changes: 31 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Nothing rocket-science, but we live in an era where you can retrofit your own ha

My favorite bit is I can just flip a T3 switch and see a floating map of the throttle's current bindings. The map reads its labels from the active configuration, so remapped controls are reflected in the floating window and I just flip the switch off and it instantly vanishes.

![Joydex floating CM3 quick-reference map](docs/images/joydex-button-map.png)
<img src="docs/images/joydex-button-map.png" alt="Joydex floating CM3 quick-reference map" width="640">

## Quick start

Expand Down Expand Up @@ -54,11 +54,11 @@ flowchart LR

Screenshot of the UI bindings, but you can also just ask Codex to configure them for you if you wanted. UI is so much easier when it's just some "oh hey set M2 B3 to reject" and your agent does the rest.

![Joydex configuration window showing the CM3 device and action mappings](docs/images/joydex-configuration.png)
<img src="docs/images/joydex-configuration.png" alt="Joydex configuration window showing the CM3 device and action mappings" width="640">

The configuration window keeps bindings, prompt pickers, button maps, and general settings on separate tabs. Software banks live under **General → Advanced** because they are only needed for hardware modes that reuse logical button numbers.

![Joydex General tab showing Safety and Open plus collapsed advanced software banks](docs/images/joydex-general-configuration.png)
<img src="docs/images/joydex-general-configuration.png" alt="Joydex General tab showing Safety and Open plus collapsed advanced software banks" width="640">

FWIW, the checked-in code demonstrates:

Expand All @@ -83,7 +83,7 @@ This led to several concrete edge cases:

Those cases are covered in the test suite. The code remains small enough to trace from a DirectInput event through binding resolution to the final `SendInput` calls.

![Joydex dry-run inspector showing raw CM3 input and resolved actions](docs/images/joydex-dry-run.png)
<img src="docs/images/joydex-dry-run.png" alt="Joydex dry-run inspector showing raw CM3 input and resolved actions" width="640">

## Included CM3 layout

Expand All @@ -105,6 +105,8 @@ The floating map reads its labels from the active configuration, so remapped con

The LEDs turn the throttle into a ten-slot task monitor. Four primary tasks use B1, B2, B4, and B5 across M2-M4; those positions stay dark until assigned, while B3 and B6 retain each bank's normal color. Six overflow tasks use B1-B6 on M1, where every unassigned button stays dark. M5 remains available for ordinary commands with a medium-pink baseline. The Alpha grip LED shows the highest-priority state across all ten slots.

When a primary task clears, its button remains dark for five seconds before the earliest M1 overflow task moves into that position. The remaining overflow tasks then compact in their existing order.

Dim gray means running, yellow means the task needs attention, and low green means completed. Red is reserved for a future fault source. Pressing an assigned button opens that Codex task. Running and attention states remain assigned after navigation; opening a completed task clears its slot and restores the button's normal binding. Here is [my CM3 lit with a few example tasks](docs/images/20260720_064606c.jpg).

### Set up task-status LEDs
Expand All @@ -118,7 +120,7 @@ This part matches the CM3 throttle, Alpha/WarBRD stick, and VIRPIL Controls Link
5. Make sure **Task alerts** is checked in the top level of the Joydex tray menu.
6. Submit a test prompt in Codex. The Task Alerts **Event stream** should record it, **Current state** should gain a running assignment, and the corresponding LED should light.

![Joydex task-alert status window showing current task assignments and integration controls](docs/images/joydex-task-alerts.png)
<img src="docs/images/joydex-task-alerts.png" alt="Joydex task-alert status window showing current task assignments and integration controls" width="640">

Joydex sends a complete snapshot whenever a task or physical mode changes, and LinkTool holds the matching colors. A read-only VIRPIL Software Link report tells Joydex which M1-M5 position is selected, so turning the dial switches LED pages without writing to controller firmware or profiles.

Expand All @@ -132,15 +134,36 @@ The tray's **Prompt pickers...** editor supports up to three named prompt lists.
- Insert types the selected prompt at the current Codex caret. A per-prompt option can run the resolved Codex Submit action afterward; it is off by default.
- **[Exit / Nevermind]**, Escape, or another controller button closes the picker without typing or submitting.

![Joydex prompt-picker tab showing the default EN3, EN2, and EN1 controls](docs/images/joydex-prompt-pickers.png)
<img src="docs/images/joydex-prompt-pickers.png" alt="Joydex prompt-picker tab showing the default EN3, EN2, and EN1 controls" width="640">

Configured devices reconnect independently. Each supported device map has its own tray item, floating window position, and optional hold-to-show control from any configured controller. Configure it in **Configure Joydex → Button Maps**: select the target map row, choose its **Hold source**, click **Capture hold-to-show**, then move the control. The CM3 and Alpha/WarBRD maps can be visible at the same time.

The checked-in [advanced configuration](config/joydex.advanced.example.json) is a sanitized copy of a working CM3 plus Alpha/WarBRD setup. It demonstrates two device profiles, cross-device map controls, two prompt pickers, custom task navigation, and two-notch scrolling. Its device GUIDs are removed and dry run is enabled. The prompt lists show one real workflow and are meant to be edited.

![Joydex Button Maps tab showing a CM3 hold-to-show control](docs/images/joydex-button-maps-configuration.png)
<img src="docs/images/joydex-button-maps-configuration.png" alt="Joydex Button Maps tab showing a CM3 hold-to-show control" width="640">

<img src="docs/images/joydex-alpha-button-map.png" alt="Joydex Alpha/WarBRD floating button map" width="640">

## Experimental wireless touchscreen

Joydex also includes an experimental ESPHome example for the
`ESP32-4848S040C_I`: a 4-inch, 480×480 capacitive touchscreen that joins the
normal 2.4 GHz LAN. It displays the same four primary task states as the
throttle LEDs and provides touch controls for Task 1 through Task 4 plus PLAN
MODE. It talks directly to Joydex through authenticated REST and Server-Sent
Events; Home Assistant and MQTT are not required.

Screenshot of it working (Magic card for scale):

<img src="docs/images/joydex-esp32-4848s040c-in-action.jpg" alt="ESP32-4848S040C_I running the Joydex bridge-console skin beside a Magic: The Gathering card for scale" width="640">

The example was physically tested July 25–27, 2026 on one panel purchased from this [AliExpress listing](https://www.aliexpress.us/item/3256808028364930.html).
I have no affiliation with the seller, it could be malware-laden, I dunno, but I've bought a number of these for home assistant projects and use them in different spots. YMMV. Listings and board
revisions can change, so verify the `ESP32-4848S040C_I` model (or ask your agent to figure it out for you). I used the [GUITION specification](https://www.guition.com/ku/icms/upload/fb081940d6fc11f09850077a33e1404f/FTPData/UEditor/file/2026121/1768961092477/ESP32-4848S040%20Specifications-EN.pdf) for the one I tested.

Start with the [ESPHome firmware guide](firmware/esphome/README.md). The repository includes a neutral white skin and a dark bridge-console skin. Both require local credentials, a trusted LAN, and a private unit-specific factory backup. Compiled firmware is deliberately not distributed because it embeds the credentials used to join and manage the panel.

![Joydex Alpha/WarBRD floating button map](docs/images/joydex-alpha-button-map.png)
The [wireless research record](docs/WIRELESS_TOUCHSCREEN_RESEARCH_V1.1.md) explains the direct ESPHome approach, while the [device reference](docs/ESP32_4848S040C_I_DEVICE_REFERENCE.md) records the tested timing, pins, flashing, redraw, and recovery findings.

## Build and explore the source

Expand Down
10 changes: 10 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ The Alpha/WarBRD button-map artwork in `docs/images/joydex-button-map_vpc-conste
Joydex's volatile LED feature-report implementation is informed by the Apache-2.0-licensed [VLEDCONTROL](https://github.com/Nereid42/VLEDCONTROL) project by Nereid42. Joydex contains a separately adapted implementation for its limited task-alert use case.

Joydex uses [HidSharp](https://github.com/SeekHisKingdom/HIDSharp), copyright 2010-2025 James F. Bellinger, under the Apache License 2.0, to open VIRPIL HID feature-report streams.

## Wireless-panel scale photograph

The photograph in
`docs/images/joydex-esp32-4848s040c-in-action.jpg` was created for Joydex by
Mattie Casper. The depicted Magic: The Gathering card is included only as a
familiar size reference. Its artwork, text, product name, and trademarks remain
the property of their respective rights holders and are not licensed under the
Joydex MIT License. Those rights holders are not affiliated with or endorsing
Joydex.
Loading
Loading