OLED button-model + arrow-chrome refactor on top of v0.6.1. Strict
KEY0/KEY1 nav on every screen, KEY0+KEY1 chord reboot, per-screen
cycling moved to controller buttons (Triangle on Trigger Test, R1 on
Lightbar). Visual chrome strip with > / < arrows on the left edge.
Web preview mirrored in lockstep + orange-tint indicator for screens
whose firmware feature report Chrome WebHID can't expose.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Bump screen count 10 → 11 in the OLED Display Add-on section to
reflect the CPU/Clock screen added in v0.6.1.
- Update the cycle order to include CPU/Clock between Diagnostics and
BT Signal.
- Drop the per-screen ASCII mockups (they were noisy and out of date)
and link each screen section to a consistent web-preview screenshot
under assets/oled/. Eleven screenshots, named oled_sc01.jpg through
oled_sc11.jpg, captured from the OLED Preview tab of the web config
so every screen looks the same and the chrome/layout matches the
newly-flashed firmware.
- Rewrite the "KEY1 behavior by screen" table as a single "Button
reference" table. K0 = next, K1 = back, K1 long-press = brightness
cycle, K0+K1 hold = reboot. The per-screen state-change actions
(Triangle / R1 / D-pad) live on the controller now and are
documented per screen.
- Update Soft-reboot Recovery + the top-level Features bullet to
reflect KEY0+KEY1 chord replacing KEY0 double-click.
- Update per-screen descriptions: Lightbar uses R1 to cycle mode (not
K1); Trigger Test uses Triangle to cycle preset (not K1); Settings
text updated to mention the new D-pad cursor behavior on the web
preview.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a dedicated "🛠️ Web Config Tool" section near the top of the
README so the deep link to https://marcelinevpq.github.io/DS5Dongle-
OLED-Config-Web/#config is discoverable. Lists the three tabs (Flash
Firmware / Config / OLED Preview) and what each one does end-to-end:
a brand-new Pico 2 W can go from "out of the box" to flashed and
configured without touching a terminal.
- BOOTSEL mode callout explaining what the physical button is, how to
enter the mode, and what the user sees (Pico mounts as a removable
drive). First-time flashers were getting stuck on this step.
- Performance / Overclocking section reworded: lead with "you don't
need to do anything — the overclock is baked into the firmware".
Stock UF2 sets 320 MHz @ 1.20 V automatically via vreg_set_voltage
and set_sys_clock_khz in src/main.cpp. The "if it fails to boot,
raise voltage / lower clock" line is scoped to people compiling
from source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refactor the Pico-OLED-1.3 button model and visual layout:
- KEY0 single press = "next screen" (double-click reboot path removed —
it was easy to fire by accident while paging quickly).
- KEY1 short press = "previous screen" on every screen. The old
contextual cycle on Trigger Test (cycle trigger preset) and Lightbar
(cycle lb_mode) moves to controller buttons:
* △ rising edge on Trigger Test → cycle trigger preset
(re-applies via send_trigger_effect)
* R1 rising edge on Lightbar → cycle lb_mode
△ on Lightbar stays as "save current RGB to favorite slot 0" (existing
UX), so R1 is used there instead.
- KEY1 long press = cycle brightness — unchanged.
- KEY0 + KEY1 held simultaneously for >= 1 s → watchdog_reboot. Pre-empts
per-key handlers so a chord cancels any armed single press. DS5
PS+Mute hold-2 s remains the headless backup.
Visual layout:
- New draw_button_chrome() paints '>' at (0, 8) and '<' at (0, 49) on
every frame from flush_fb(), pairing the on-screen labels with the
physical KEY0/KEY1 buttons on the left edge of the device.
- Every render_screen_*() shifts main content right by kContentX (6 px)
to clear the chrome strip. Stick boxes, L2 column, L1 bar, and D-pad
center on the Status screen also shift right by 6 to avoid the '<'
glyph painting inside the live left-stick area.
- "K0=next K1=back" footers removed from all screens (the chrome strip
is now the affordance). Trigger Test footer = "Tri=cycle"; Lightbar
footer = "R1=mode"; Slots/Settings keep their contextual hints.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build-pr/ is a parallel CMake build tree that survives between PR
investigations. Added alongside build/ to keep it out of the index.
Also ignore four root-level files that are inherently per-user:
.claude/ (Claude Code config), .directory (KDE Dolphin metadata),
*.code-workspace (VS Code workspace files), pr-review-*.md (local
scratch review notes).
Drops VS Code's untracked-file count for a fresh clone + build run
from ~1760 to 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CPU/Clock OLED diagnostics screen (PR #2) + battery_led disconnect
fix, on top of v0.6.0-oled-edition. Promotes the [Unreleased] section
to [0.6.1-oled-edition] with today's date.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to #2 — the PR added a CPU/Clock OLED screen and bumped
kNumScreens from 10 to 11, but README still advertised 10 screens
in two places (header blurb + Features bullet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Future releases get:
- A 'Checksums' section appended to the release body with sha256sum -c
-compatible 'HASH filename' lines for each .uf2 attached
- A SHA256SUMS.txt file uploaded alongside the .uf2 artifacts so users
can verify with 'sha256sum -c SHA256SUMS.txt' after downloading
Web-config flasher already compares this against its own SHA-256 on the
'Use latest release' path, but the hashes on the release page let people
manually verify the bundled site UF2 too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a repository_dispatch step to the release workflow that pings the
DS5Dongle-OLED-Config-Web repo with event-type 'firmware-released' after
the UF2s are uploaded. The web repo's deploy.yml listens for that event
and re-runs its build, which re-fetches the latest release UF2 and
bundles it into the GH Pages site.
Requires a fine-grained PAT stored as WEB_REPO_DISPATCH_PAT secret on
this repo, scoped to:
- Repository: MarcelineVPQ/DS5Dongle-OLED-Config-Web
- Permissions: Actions: Write, Metadata: Read
If the secret isn't set, the step fails 401 and continue-on-error
swallows it — release upload still succeeds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an [Unreleased] block for the fb68ea5 disconnect fix (cross-references
the upstream PR awalol/DS5Dongle#101), and a [0.6.0-oled-edition] entry
covering the GitHub release / tag that wraps the existing 0.6.0-rebase code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The low-battery blink would stay frozen mid-cycle (or briefly resume
during reconnect retries) after a controller died and lost connection,
because the stale-report check stopped toggling but never turned the
LED off, and the cached interrupt_in_data[52] still read low.
New `battery_led_on_disconnect()` clears blink state, forces the LED
off, and zeros last_report_us so the stale-check early-return blocks
any new blink until a fresh 0x31 report arrives on the next connection.
Called from bt.cpp's HCI_EVENT_DISCONNECTION_COMPLETE handler.
Stale-check in the tick also now forces LED off when it fires while a
blink was in progress (defense in depth for unclean disconnects).
Same bug exists in upstream awalol/DS5Dongle (their battery_led.cpp is
byte-identical to ours pre-fix) — Sura Academy reported it in their
Discord. Plan to send this back as a PR after we validate it locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README: checkout from our master since our content (10-screen
docs, web-config link, multi-slot docs, Auto Haptics docs,
Acknowledgements, etc.) is the source of truth for the fork; upstream's
v0.6.0 README updates are mostly about their new web config tool which
we don't surface (we link our own fork's web config instead).
CHANGELOG: new [0.6.0-rebase] entry on top documenting (a) the
state_mgr adoption + speaker-fix hack drop, (b) the discoverable-rule
loosening for multi-slot, (c) the iSerialNumber restore that fixes
#100, and (d) verification results from the mid-rebase flash.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build now compiles. audio.cpp on top of upstream's state_mgr-based
audio_loop adds three additive pieces:
1. VU peak meters for ch0/1 (spk_max) and ch2/3 (hap_max) tracked
per-sample in the existing loop; written back to g_peak_spk /
g_peak_hap after. Accessors decay 12.5% per read so the OLED
VU bars fall back naturally over a few frames.
2. Monotonic byte-flow counters: g_usb_frames++ after tud_audio_read
(the OLED Diagnostics screen + web emulator compute USB-aud/s
and BT-0x32/s as delta over time). g_bt_packets++ after each
0x36 bt_write.
3. Audio Auto Haptics DSP — 1-pole LP + envelope follower +
modulation + soft-clip per loteran/DS5Dongle 5d6bc2f, with our
added 4th "Fallback" mode that fires only after the game's
native haptic path has been silent for ~1 s (NATIVE_SILENT_TIMEOUT
= 100 audio_loop calls). Preserves native HD haptics (Spider-Man
Remastered) while filling in for games that send no haptic data
(Ghost of Tsushima). Modes: 0=Off, 1=Fallback (default), 2=Mix,
3=Replace.
Crucially the state_mgr-based state_set(pkt+13, 63) call is left
untouched — that's upstream's correct replacement for our prior
state_data restore hack, and it's load-bearing for speaker output.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bt.h: add OLED accessors (bt_is_connected, bt_get_addr, bt_hci_err_count)
and 6 multi-slot accessors (bt_get_slot, bt_set_slot, bt_forget_slot,
bt_wipe_all_slots, bt_slot_occupied, bt_slot_get_addr).
bt.cpp:
- #include "slots.h"; add g_current_slot + update_discoverable()
helper that gates gap_discoverable_control on slots_any_empty().
- 3 OLED accessor impls + 6 slot accessor impls.
- bt_init: slots_load() + g_current_slot from Config_body.current_slot,
then update_discoverable() instead of bare gap_discoverable_control(1).
- HCI_EVENT_INQUIRY_RESULT: slot-ownership filter (skip devices owned
by other slots; if our slot is occupied, only accept matching addr).
- L2CAP_EVENT_CHANNEL_OPENED HID_CONTROL: slot_assign on empty slot
(Phase G auto-bond pattern).
- L2CAP_EVENT_CHANNEL_OPENED HID_INTERRUPT: replace upstream's
gap_*_control(false) pair with update_discoverable() — they wanted
"non-discoverable once connected"; we want "non-discoverable once
all 4 slots are full". Strictly looser, which is correct since
empty slots need to accept new pairings.
- Inquiry-complete + disconnection-complete handlers similarly
swapped to update_discoverable().
Upstream's state_mgr hooks (state_init / state_set / state_update
calls in L2CAP_EVENT_CHANNEL_OPENED and elsewhere) are preserved
as-is — they're the upstream rumble-fix infrastructure.
Build still fails on oled.cpp references to audio_peak_* / counter
accessors that Phase E adds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three additions on top of upstream's v0.6.0-hotfix main.cpp:
- #include "oled.h" + oled_init() after audio_init/state_init,
oled_loop() in the main while(1).
- PS + Mute hold-2-seconds combo at the top of interrupt_loop()
for headless soft-reboot via watchdog_reboot(0,0,0). Bit pattern
(interrupt_in_data[9] & 0x05) is rare in normal gameplay so the
long hold prevents accidental triggers.
Upstream's state_init() call sits right between audio_init() and our
new oled_init() — preserved as-is. The state_mgr → 0x31 output report
flow in tud_hid_set_report_cb is upstream's rumble path; not touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build-system, config, usb, and CI wiring on top of upstream's
v0.6.0-hotfix tree. Build will still fail until Phase C+D land bt.h
accessors that oled.cpp references; that's expected mid-rebase.
CMakeLists.txt: add src/oled.cpp + src/slots.cpp to add_executable,
link hardware_spi, restore OUTPUT_NAME ds5-bridge-oled.
src/config.h: append the 4 OLED Edition Config_body fields
(current_slot + auto_haptics_enable/gain/lowpass). Preserves
upstream's field order for the first 8 fields.
src/config.cpp:
- speaker_volume validity default -100 dB → 0 dB (footgun fix)
- 4 new validity guards for the appended fields with sensible
defaults (Fallback / 100 % / 160 Hz / slot 0)
- config_default() impl (was declared in upstream's config.h since
v0.5.4 but never defined; the OLED Edition's "Reset to defaults"
Settings item calls it)
src/usb.cpp: drop the UAC1 SET_CUR Volume → flash-config sync that
let PipeWire/Pulse silently override the user's saved Spk Vol on
every device reconnect. Borrowed from loteran/DS5Dongle commit
03fa1e4.
src/usb_descriptors.cpp: restore iSerialNumber = STRID_SERIAL.
Upstream's commit e79c762 zeroed it to work around SpecialK (#32),
but that broke Windows device identity (#100) — users lost
per-device volume / app preferences when moving USB ports. The
OLED Edition prioritizes Windows device-identity stability for
the broader user base over SpecialK compat for a narrower one.
.github/workflows/{build,release}.yml: artifact filenames use
ds5-bridge-oled.uf2 (and -debug / -picow variants) per the
OUTPUT_NAME rebrand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>