fix: adaptive triggers + opt-in OLED sleep + brightness persistence

Closes the three non-audio user issues (audio path untouched):

- #6: adaptive trigger FFB was silently dropped. state_update() copied the
  RightTriggerFFB/LeftTriggerFFB params into the outgoing state but never set
  the Allow{Right,Left}TriggerFFB apply-bits in byte 0, so the DS5 discarded
  them on BOTH the standalone 0x31 path and the 0x36 audio-frame fold, while
  direct USB worked. Mirror the host's two allow-flags in, like the rumble
  flags already were (matches what the on-device Trigger Test screen does).

- #8/#9: new CtrlWake setting (default on = unchanged behavior). Set off and
  controller input no longer keeps the OLED awake, so the dim/off timeouts run
  during gameplay and the panel can sleep while the controller is in use; only
  KEY0/KEY1 wake it.

- #9: OLED brightness (screen_brightness) now persists across a power cycle
  instead of resetting to full on every boot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MarcelineVPQ
2026-06-02 18:58:33 -06:00
co-authored by Claude Opus 4.8
parent 8fc369b1af
commit 71cead401d
5 changed files with 64 additions and 6 deletions
+9
View File
@@ -8,6 +8,15 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
## [Unreleased] ## [Unreleased]
### Added
- **`CtrlWake` setting — let the OLED sleep while the controller is in use (issues #8, #9).** The idle power-ladder (dim → off) previously never fired during gameplay, because every controller input bumped the activity timer and kept the panel awake — so a configured dim/off timeout effectively did nothing while playing. New Settings item `CtrlWake` (default **on**, preserving the old behavior); set it **off** and controller input no longer wakes the screen — only the OLED's KEY0/KEY1 do — so the dim/off timers count down during play and the panel can sleep, requiring a button press to wake.
### Fixed
- **Adaptive trigger effects now work through the dongle (issue #6).** Host-sent trigger force-feedback was silently dropped: `state_update()` copied the 11-byte `RightTriggerFFB`/`LeftTriggerFFB` parameter blocks into the outgoing controller-state but never set the `AllowRightTriggerFFB`/`AllowLeftTriggerFFB` "apply" bits in byte 0, so the DualSense received the data with the enable flags cleared and discarded it. Affected **both** output paths (standalone `0x31` and the `0x36` audio-frame fold), which is why triggers felt absent through the dongle but worked on direct USB. The on-dongle Trigger Test screen always set those bits directly (`0x0C`), which is why it worked. Now the host's two allow-flags are mirrored into the state alongside the FFB data, like the rumble flags already were.
- **OLED brightness now persists across a power cycle (issue #9).** The KEY1-long-press brightness level was a runtime-only value that reset to full on every boot; it is now stored in config and restored at init.
--- ---
## [0.6.10-oled-edition] — 2026-05-25 ## [0.6.10-oled-edition] — 2026-05-25
+8
View File
@@ -113,6 +113,14 @@ void config_valid() {
body->bt_mic_enable = 1; body->bt_mic_enable = 1;
printf("[Config] bt_mic_enable invalid, defaulting to 1 (on)\n"); printf("[Config] bt_mic_enable invalid, defaulting to 1 (on)\n");
} }
if (body->screen_brightness > 3) { // kBrightLevels has 4 entries (0..3)
body->screen_brightness = 0; // full brightness
printf("[Config] screen_brightness invalid, defaulting to 0 (full)\n");
}
if (body->controller_wakes_display > 1) { // 0xFF erased / upgrade → default ON
body->controller_wakes_display = 1;
printf("[Config] controller_wakes_display invalid, defaulting to 1 (on)\n");
}
if (body->config_version != CONFIG_VERSION) { if (body->config_version != CONFIG_VERSION) {
body->config_version = CONFIG_VERSION; body->config_version = CONFIG_VERSION;
printf("[Config] Warning: Config may breaking change\n"); printf("[Config] Warning: Config may breaking change\n");
+10
View File
@@ -44,6 +44,16 @@ struct __attribute__((packed)) Config_body {
// over BT and the dongle decodes it to the USB capture endpoint. Costs extra // over BT and the dongle decodes it to the USB capture endpoint. Costs extra
// DS5 battery (keeps its audio subsystem awake), hence the toggle. // DS5 battery (keeps its audio subsystem awake), hence the toggle.
uint8_t bt_mic_enable; uint8_t bt_mic_enable;
// OLED brightness, as an index into kBrightLevels[] (src/oled.cpp). Persisted
// so the KEY1-long-press brightness choice survives a power cycle. Erased
// flash (0xFF) → clamped to 0 (full brightness) by config_valid. Issue #9.
uint8_t screen_brightness;
// When 0, controller input no longer keeps the OLED awake — only the OLED's
// own KEY0/KEY1 do — so the dim/off timers actually count down during
// gameplay and the panel can sleep while the controller is in use. Default 1
// preserves the original "any controller activity wakes the screen"
// behavior. Issues #8 (dim timeout never fired during play) and #9.
uint8_t controller_wakes_display;
}; };
struct __attribute__((packed)) Config { struct __attribute__((packed)) Config {
+27 -6
View File
@@ -126,15 +126,16 @@ constexpr int kLbModeHost = 8;
constexpr int kNumLbModes = 9; constexpr int kNumLbModes = 9;
// Settings screen state // Settings screen state
constexpr int kNumSettingsItems = 16; // 8 fields + 3 auto-haptic + 2 screen-timeout + BT mic + Reset + Wipe constexpr int kNumSettingsItems = 17; // 8 fields + 3 auto-haptic + 2 screen-timeout + BT mic + Ctrl-wake + Reset + Wipe
constexpr int kSettingsAutoHapEnaIdx = 8; constexpr int kSettingsAutoHapEnaIdx = 8;
constexpr int kSettingsAutoHapGainIdx = 9; constexpr int kSettingsAutoHapGainIdx = 9;
constexpr int kSettingsAutoHapLpIdx = 10; constexpr int kSettingsAutoHapLpIdx = 10;
constexpr int kSettingsScrDimIdx = 11; constexpr int kSettingsScrDimIdx = 11;
constexpr int kSettingsScrOffIdx = 12; constexpr int kSettingsScrOffIdx = 12;
constexpr int kSettingsBtMicIdx = 13; constexpr int kSettingsBtMicIdx = 13;
constexpr int kSettingsResetIdx = 14; constexpr int kSettingsCtrlWakeIdx = 14;
constexpr int kSettingsWipeSlotsIdx = 15; constexpr int kSettingsResetIdx = 15;
constexpr int kSettingsWipeSlotsIdx = 16;
Config_body settings_local{}; Config_body settings_local{};
int settings_sel = 0; int settings_sel = 0;
bool settings_dirty = false; bool settings_dirty = false;
@@ -521,6 +522,14 @@ void handle_buttons() {
last_activity_us = time_us_64(); last_activity_us = time_us_64();
if (held > kLongPressUs) { if (held > kLongPressUs) {
bright_idx = (bright_idx + 1) % kNumBrightLevels; bright_idx = (bright_idx + 1) % kNumBrightLevels;
// Persist so the choice survives a power cycle (issue #9). Keep
// settings_local in sync too, so a later Settings-screen save can't
// clobber screen_brightness with its stale snapshot.
Config_body b = get_config();
b.screen_brightness = (uint8_t)bright_idx;
set_config(b);
config_save();
settings_local.screen_brightness = (uint8_t)bright_idx;
} else { } else {
current_screen = (current_screen - 1 + kNumScreens) % kNumScreens; current_screen = (current_screen - 1 + kNumScreens) % kNumScreens;
last_render_us = 0; last_render_us = 0;
@@ -1547,6 +1556,7 @@ void settings_adjust(int delta) {
break; break;
} }
case 13: c.bt_mic_enable ^= 1; break; // BT mic on/off case 13: c.bt_mic_enable ^= 1; break; // BT mic on/off
case 14: c.controller_wakes_display ^= 1; break; // controller activity wakes OLED on/off
} }
} }
@@ -1649,8 +1659,9 @@ __attribute__((noinline)) void format_settings_item(int idx, char* line, size_t
else snprintf(line, n, "%s ScrOff %umin", cur, c.screen_off_timeout); else snprintf(line, n, "%s ScrOff %umin", cur, c.screen_off_timeout);
break; break;
case 13: snprintf(line, n, "%s BT Mic %s", cur, c.bt_mic_enable ? "on" : "off"); break; case 13: snprintf(line, n, "%s BT Mic %s", cur, c.bt_mic_enable ? "on" : "off"); break;
case 14: snprintf(line, n, "%s Reset to defaults", cur); break; case 14: snprintf(line, n, "%s CtrlWake %s", cur, c.controller_wakes_display ? "on" : "off"); break;
case 15: snprintf(line, n, "%s Wipe all slots", cur); break; case 15: snprintf(line, n, "%s Reset to defaults", cur); break;
case 16: snprintf(line, n, "%s Wipe all slots", cur); break;
} }
} }
@@ -1822,6 +1833,11 @@ void oled_init() {
// Restore the persisted lightbar mode + favorites (config_load() already ran // Restore the persisted lightbar mode + favorites (config_load() already ran
// in main() before this). Defaults to HOST passthrough on a fresh flash. // in main() before this). Defaults to HOST passthrough on a fresh flash.
lightbar_load_config(); lightbar_load_config();
// Restore the persisted OLED brightness (KEY1-long-press choice). config_valid
// clamps screen_brightness to a legal kBrightLevels index, so this is safe to
// use directly. Fresh flash → 0 (full brightness). Issue #9.
bright_idx = get_config().screen_brightness;
} }
// Dim-tier renderer: blank the panel and draw a tiny "I'm alive" dot that // Dim-tier renderer: blank the panel and draw a tiny "I'm alive" dot that
@@ -1885,7 +1901,12 @@ void oled_loop() {
} }
if (hash != last_input_hash) { if (hash != last_input_hash) {
last_input_hash = hash; last_input_hash = hash;
last_activity_us = time_us_64(); // Controller input only keeps the panel awake when the user has left
// "CtrlWake" on (the default). With it off, the dim/off timers count
// down during gameplay and only KEY0/KEY1 wake the screen — see
// handle_buttons(), which bumps last_activity_us unconditionally.
// Issues #8 / #9.
if (get_config().controller_wakes_display) last_activity_us = time_us_64();
} }
// Rising-edge: BT-connect itself counts as activity, so the screen wakes // Rising-edge: BT-connect itself counts as activity, so the screen wakes
// the moment a controller pairs rather than waiting for the first input. // the moment a controller pairs rather than waiting for the first input.
+10
View File
@@ -82,6 +82,16 @@ void state_update(const uint8_t *data, const uint8_t size) {
set_bit(state[0], 0, update.EnableRumbleEmulation); set_bit(state[0], 0, update.EnableRumbleEmulation);
set_bit(state[0], 1, update.UseRumbleNotHaptics); set_bit(state[0], 1, update.UseRumbleNotHaptics);
// Mirror the host's adaptive-trigger "apply" flags into the outgoing state.
// Without these, copy_if_allowed below writes the 11-byte FFB params into
// state[] but the DS5 receives them with AllowRight/LeftTriggerFFB cleared
// and discards them — so adaptive triggers were dead through the dongle on
// BOTH the standalone 0x31 path and the 0x36 audio fold, while direct USB
// (which carries the host's flags verbatim) worked. The on-device Trigger
// Test screen sets these same bits (0x0C) directly, which is why it worked.
// (issue #6)
set_bit(state[0], 2, update.AllowRightTriggerFFB);
set_bit(state[0], 3, update.AllowLeftTriggerFFB);
set_bit(state[38], 2, update.EnableImprovedRumbleEmulation); set_bit(state[38], 2, update.EnableImprovedRumbleEmulation);
copy_if_allowed( copy_if_allowed(
update.UseRumbleNotHaptics || update.EnableRumbleEmulation, update.UseRumbleNotHaptics || update.EnableRumbleEmulation,