Files
DS5Dongle-OLED-Edition-stea…/src/slots.cpp
T
MarcelineVPQandClaude Opus 4.7 4b6a72b70a rebase: Phase B — trivial files for the v0.6.0 base
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>
2026-05-17 07:53:41 -06:00

117 lines
3.3 KiB
C++

// Flash-backed slot table for 4-slot persistent BT pairing.
// Modeled on zurce/DS5Dongle-OLED (bt.cpp:29-115). Credit to zurce.
#include "slots.h"
#include <cstring>
#include <cstdio>
#include "hardware/flash.h"
#include "hardware/sync.h"
constexpr uint32_t SLOTS_MAGIC = 0x44533502u; // "DS5\x02"
constexpr uint32_t SLOTS_FLASH_OFFSET = PICO_FLASH_SIZE_BYTES - 2u * FLASH_SECTOR_SIZE;
struct __attribute__((packed)) SlotsData {
uint32_t magic;
uint8_t addrs[kNumSlots][6];
uint8_t occupied[kNumSlots];
};
static_assert(sizeof(SlotsData) <= FLASH_PAGE_SIZE);
static_assert(SLOTS_FLASH_OFFSET % FLASH_SECTOR_SIZE == 0);
static SlotsData g_slots{};
static const SlotsData *flash_slots() {
return reinterpret_cast<const SlotsData *>(XIP_BASE + SLOTS_FLASH_OFFSET);
}
static bool save_slots_to_flash() {
alignas(4) uint8_t page[FLASH_PAGE_SIZE];
memset(page, 0xff, sizeof(page));
memcpy(page, &g_slots, sizeof(g_slots));
const uint32_t interrupts = save_and_disable_interrupts();
flash_range_erase(SLOTS_FLASH_OFFSET, FLASH_SECTOR_SIZE);
flash_range_program(SLOTS_FLASH_OFFSET, page, sizeof(page));
restore_interrupts(interrupts);
SlotsData verify{};
memcpy(&verify, flash_slots(), sizeof(verify));
if (memcmp(&verify, &g_slots, sizeof(g_slots)) == 0) {
printf("[Slots] flash write verified\n");
return true;
}
printf("[Slots] flash write VERIFY FAILED\n");
return false;
}
void slots_load() {
memcpy(&g_slots, flash_slots(), sizeof(g_slots));
if (g_slots.magic != SLOTS_MAGIC) {
printf("[Slots] flash sector empty/invalid, initializing\n");
memset(&g_slots, 0, sizeof(g_slots));
g_slots.magic = SLOTS_MAGIC;
save_slots_to_flash();
}
for (int i = 0; i < kNumSlots; i++) {
if (g_slots.occupied[i]) {
printf("[Slots] %d: %02X:%02X:%02X:%02X:%02X:%02X\n", i,
g_slots.addrs[i][0], g_slots.addrs[i][1], g_slots.addrs[i][2],
g_slots.addrs[i][3], g_slots.addrs[i][4], g_slots.addrs[i][5]);
} else {
printf("[Slots] %d: (empty)\n", i);
}
}
}
bool slot_occupied(int slot) {
if (slot < 0 || slot >= kNumSlots) return false;
return g_slots.occupied[slot] != 0;
}
void slot_get_addr(int slot, uint8_t out[6]) {
if (slot < 0 || slot >= kNumSlots) {
memset(out, 0, 6);
return;
}
memcpy(out, g_slots.addrs[slot], 6);
}
int slot_owner_of(const uint8_t addr[6]) {
for (int i = 0; i < kNumSlots; i++) {
if (g_slots.occupied[i] && memcmp(g_slots.addrs[i], addr, 6) == 0) return i;
}
return -1;
}
void slot_assign(int slot, const uint8_t addr[6]) {
if (slot < 0 || slot >= kNumSlots) return;
memcpy(g_slots.addrs[slot], addr, 6);
g_slots.occupied[slot] = 1;
save_slots_to_flash();
}
void slot_forget(int slot) {
if (slot < 0 || slot >= kNumSlots) return;
memset(g_slots.addrs[slot], 0, 6);
g_slots.occupied[slot] = 0;
save_slots_to_flash();
}
void slots_wipe_all() {
for (int i = 0; i < kNumSlots; i++) {
memset(g_slots.addrs[i], 0, 6);
g_slots.occupied[i] = 0;
}
save_slots_to_flash();
}
bool slots_any_empty() {
for (int i = 0; i < kNumSlots; i++) {
if (!g_slots.occupied[i]) return true;
}
return false;
}