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>
117 lines
3.3 KiB
C++
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;
|
|
}
|