Files
DS5Dongle-OLED-Edition-stea…/src/state_mgr.cpp
T
MarcelineVPQandClaude Opus 4.7 4edcd20181 feat(oled): charge ETA, persistent lightbar, charging-aware idle ladder
Status screen: show an estimated time-to-full ("~43m") while charging,
self-calibrating from each 10% battery notch with Li-ion taper correction
(discard the partial plug-in step; 3-sample moving average; per-step weight
1.0/1.5/2.2 for bulk/80-90/90-100%). Shows "~--m" until the first full step.

Lightbar: the chosen mode + 4 favorites now persist to config flash and
stick across every screen (and through gameplay/audio). A single
lightbar_service() owns the LED via the persistent state[] block (new
state_set_led/state_get_led) with a host-override gate (g_lightbar_override)
so the host's AllowLedColor can't stomp a firmware-chosen mode. New HOST
passthrough mode (default) keeps the game in control of the LED out of the
box. The charging amber pulse (255,100,0) is folded in as top priority.

Idle ladder: keep the panel at the dim/dot tier (never full-off) while
charging so users stop unplugging to wake it (which reset the charge ETA).
Fix idle detection to deadzone stick jitter [120,140] + skip the counter
byte (idata[6]) so the dot tier engages with a controller connected
(mirrors bt.cpp's inactivity heuristic).

New Config_body fields: lightbar_mode + lb_fav_{r,g,b}[4].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 19:38:51 -06:00

173 lines
5.2 KiB
C++

//
// Created by awalol on 2026/5/15.
//
#include <cstddef>
#include <cstring>
#include "utils.h"
#include "state_mgr.h"
// Set by the OLED lightbar service (src/oled.cpp). While true, the firmware
// owns the lightbar (an OLED mode or the charging pulse) and the host's
// AllowLedColor writes are suppressed below so they can't stomp it.
extern bool g_lightbar_override;
namespace {
constexpr size_t kAudioControlOffset = offsetof(SetStateData, MuteLightMode) - sizeof(uint8_t);
constexpr size_t kMuteControlOffset = offsetof(SetStateData, RightTriggerFFB) - sizeof(uint8_t);
constexpr size_t kMotorPowerLevelOffset = offsetof(SetStateData, HostTimestamp) + sizeof(uint32_t);
constexpr size_t kAudioControl2Offset = kMotorPowerLevelOffset + sizeof(uint8_t);
constexpr size_t kHapticLowPassFilterOffset = offsetof(SetStateData, LightFadeAnimation) - 2 * sizeof(uint8_t);
constexpr size_t kPlayerIndicatorsOffset = offsetof(SetStateData, LedRed) - sizeof(uint8_t);
}
static constexpr uint8_t state_init_data[63] = {
0xfd, 0xf7, 0x0, 0x0,
0x7f, 0x64, // Headphones, Speaker
0x40, 0x9, 0x0, 0x00, 0x0, 0x0, 0x0, 0x0, // VolumeMic=64, MuteControl all clear (no PowerSave)
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa,
0x7, 0x0, 0x0, 0x2, 0x1,
0x00,
0xff, 0xd7, 0x00 // RGB LED: R, G, B (Nijika Color!)✨
};
uint8_t state[63]{};
void state_init() {
memcpy(state, state_init_data, sizeof(state));
}
void state_set(uint8_t *data, const uint8_t size) {
if (size > 63) {
printf("[StateMgr] Warning: State Set over 63 bytes\n");
}
memcpy(data, state, size);
}
void state_set_led(uint8_t r, uint8_t g, uint8_t b) {
state[offsetof(SetStateData, LedRed) + 0] = r;
state[offsetof(SetStateData, LedRed) + 1] = g;
state[offsetof(SetStateData, LedRed) + 2] = b;
}
void state_get_led(uint8_t *r, uint8_t *g, uint8_t *b) {
*r = state[offsetof(SetStateData, LedRed) + 0];
*g = state[offsetof(SetStateData, LedRed) + 1];
*b = state[offsetof(SetStateData, LedRed) + 2];
}
void state_update(const uint8_t *data, const uint8_t size) {
if (size < sizeof(SetStateData)) {
printf(
"[StateMgr] Error: SetStateData at least %u bytes\n",
static_cast<unsigned>(sizeof(SetStateData))
);
return;
}
SetStateData update{};
memcpy(&update, data, sizeof(update));
const auto copy_if_allowed = [&](const bool allowed, const size_t offset, const size_t length) {
if (allowed) {
memcpy(state + offset, data + offset, length);
}
};
auto set_bit = [](uint8_t &byte, const int bit, const bool value) {
byte = (byte & ~(1 << bit)) | (value << bit);
};
set_bit(state[0], 0, update.EnableRumbleEmulation);
set_bit(state[0], 1, update.UseRumbleNotHaptics);
set_bit(state[38], 2, update.EnableImprovedRumbleEmulation);
copy_if_allowed(
update.UseRumbleNotHaptics || update.EnableRumbleEmulation,
offsetof(SetStateData, RumbleEmulationRight),
2
);
/*copy_if_allowed(
update.AllowHeadphoneVolume,
offsetof(SetStateData, VolumeHeadphones),
sizeof(update.VolumeHeadphones)
);*/
/*copy_if_allowed(
update.AllowSpeakerVolume,
offsetof(SetStateData, VolumeSpeaker),
sizeof(update.VolumeSpeaker)
);*/
/*copy_if_allowed(
update.AllowMicVolume,
offsetof(SetStateData, VolumeMic),
sizeof(update.VolumeMic)
);*/
/*copy_if_allowed(
update.AllowAudioControl,
kAudioControlOffset,
sizeof(uint8_t)
);*/
copy_if_allowed(
update.AllowMuteLight,
offsetof(SetStateData, MuteLightMode),
sizeof(update.MuteLightMode)
);
/*copy_if_allowed(
update.AllowAudioMute,
kMuteControlOffset,
sizeof(uint8_t)
);*/
copy_if_allowed(
update.AllowRightTriggerFFB,
offsetof(SetStateData, RightTriggerFFB),
sizeof(update.RightTriggerFFB)
);
copy_if_allowed(
update.AllowLeftTriggerFFB,
offsetof(SetStateData, LeftTriggerFFB),
sizeof(update.LeftTriggerFFB)
);
/*copy_if_allowed(
update.AllowMotorPowerLevel,
kMotorPowerLevelOffset,
sizeof(uint8_t)
);*/
/*copy_if_allowed(
update.AllowAudioControl2,
kAudioControl2Offset,
sizeof(uint8_t)
);*/
/*copy_if_allowed(
update.AllowHapticLowPassFilter,
kHapticLowPassFilterOffset,
sizeof(uint8_t)
);*/
copy_if_allowed(
update.AllowColorLightFadeAnimation,
offsetof(SetStateData, LightFadeAnimation),
sizeof(update.LightFadeAnimation)
);
copy_if_allowed(
update.AllowLightBrightnessChange,
offsetof(SetStateData, LightBrightness),
sizeof(update.LightBrightness)
);
copy_if_allowed(
update.AllowPlayerIndicators,
kPlayerIndicatorsOffset,
sizeof(uint8_t)
);
copy_if_allowed(
update.AllowLedColor && !g_lightbar_override,
offsetof(SetStateData, LedRed),
sizeof(update.LedRed) * 3
);
}