#include "ble_config.h" #include #include using namespace Adafruit_LittleFS_Namespace; extern File cfgFile; // ─── BLE Config Service objects ─────────────────────────────────────────────── #ifdef FEATURE_CONFIG_SERVICE BLEService cfgService (0x1234); BLECharacteristic cfgBlob (0x1235); // ConfigBlob R/W 16 bytes BLECharacteristic cfgCommand (0x1236); // Command W 1 byte #ifdef FEATURE_TELEMETRY BLECharacteristic cfgTelemetry(0x1237); // Telemetry R/N 24 bytes #endif #ifdef FEATURE_IMU_STREAM BLECharacteristic cfgImuStream(0x1238); // ImuStream N 14 bytes #endif #endif // ─── Charge mode ────────────────────────────────────────────────────────────── void applyChargeMode(ChargeMode mode) { switch (mode) { case CHARGE_OFF: pinMode(PIN_HICHG, INPUT_PULLUP); break; case CHARGE_SLOW: pinMode(PIN_HICHG, OUTPUT); digitalWrite(PIN_HICHG, HIGH); break; case CHARGE_FAST: pinMode(PIN_HICHG, OUTPUT); digitalWrite(PIN_HICHG, LOW); break; } const char* n[] = {"OFF (~0mA)", "SLOW (50mA)", "FAST (100mA)"}; Serial.print("[CHG] "); Serial.println(n[mode]); } // ─── Config persistence ─────────────────────────────────────────────────────── void loadConfig() { InternalFS.begin(); cfgFile.open(CONFIG_FILENAME, FILE_O_READ); if (cfgFile) { cfgFile.read(&cfg, sizeof(cfg)); cfgFile.close(); if (cfg.magic != CONFIG_MAGIC) { cfg = CFG_DEFAULTS; Serial.println("[CFG] Defaults (bad magic)"); } else { Serial.println("[CFG] Loaded from flash"); } } else { cfg = CFG_DEFAULTS; Serial.println("[CFG] Defaults (no file)"); } } void saveConfig() { InternalFS.remove(CONFIG_FILENAME); cfgFile.open(CONFIG_FILENAME, FILE_O_WRITE); if (cfgFile) { cfgFile.write((uint8_t*)&cfg, sizeof(cfg)); cfgFile.close(); Serial.println("[CFG] Saved"); } else { Serial.println("[CFG] ERROR: write failed"); } } // ─── ConfigBlob push ───────────────────────────────────────────────────────── #ifdef FEATURE_CONFIG_SERVICE void pushConfigBlob() { ConfigBlob b; b.sensitivity = cfg.sensitivity; b.deadZone = cfg.deadZone; b.accelStrength = cfg.accelStrength; b.curve = (uint8_t)cfg.curve; b.axisFlip = cfg.axisFlip; b.chargeMode = (uint8_t)cfg.chargeMode; b._pad = 0; cfgBlob.write((uint8_t*)&b, sizeof(b)); } #endif void factoryReset() { cfg = CFG_DEFAULTS; saveConfig(); applyChargeMode(cfg.chargeMode); #ifdef FEATURE_CONFIG_SERVICE if (!safeMode) pushConfigBlob(); #endif #ifdef FEATURE_TELEMETRY telem = {}; #endif #ifdef FEATURE_TAP_DETECTION statLeftClicks = statRightClicks = 0; #endif Serial.println("[CFG] Factory reset complete"); } // ─── BLE callbacks ──────────────────────────────────────────────────────────── #ifdef FEATURE_CONFIG_SERVICE void onConfigBlobWrite(uint16_t h, BLECharacteristic* c, uint8_t* d, uint16_t l) { if (l != sizeof(ConfigBlob)) { Serial.println("[CFG] Bad blob length"); return; } ConfigBlob* b = (ConfigBlob*)d; cfg.sensitivity = b->sensitivity; cfg.deadZone = b->deadZone; cfg.accelStrength = b->accelStrength; if (b->curve <= 2) cfg.curve = (CurveType)b->curve; cfg.axisFlip = b->axisFlip; if (b->chargeMode <= 2) { cfg.chargeMode = (ChargeMode)b->chargeMode; applyChargeMode(cfg.chargeMode); } saveConfig(); Serial.print("[CFG] Written — sens="); Serial.print(cfg.sensitivity,0); Serial.print(" dz="); Serial.print(cfg.deadZone,3); Serial.print(" curve="); Serial.print(cfg.curve); Serial.print(" chg="); Serial.println(cfg.chargeMode); } void onCommandWrite(uint16_t h, BLECharacteristic* c, uint8_t* d, uint16_t l) { if (l < 1) return; if (d[0] == 0x01) pendingCal = true; if (d[0] == 0xFF) pendingReset = true; } #ifdef FEATURE_IMU_STREAM void onImuStreamCccd(uint16_t conn_hdl, BLECharacteristic* chr, uint16_t value) { imuStreamEnabled = (value == BLE_GATT_HVX_NOTIFICATION); Serial.print("[STREAM] "); Serial.println(imuStreamEnabled ? "ON" : "OFF"); } #endif // ─── BLE config service setup ───────────────────────────────────────────────── void setupConfigService() { cfgService.begin(); cfgBlob.setProperties(CHR_PROPS_READ | CHR_PROPS_WRITE); cfgBlob.setPermission(SECMODE_OPEN, SECMODE_OPEN); cfgBlob.setFixedLen(sizeof(ConfigBlob)); cfgBlob.setWriteCallback(onConfigBlobWrite); cfgBlob.begin(); pushConfigBlob(); cfgCommand.setProperties(CHR_PROPS_WRITE); cfgCommand.setPermission(SECMODE_OPEN, SECMODE_OPEN); cfgCommand.setFixedLen(1); cfgCommand.setWriteCallback(onCommandWrite); cfgCommand.begin(); #ifdef FEATURE_TELEMETRY cfgTelemetry.setProperties(CHR_PROPS_READ | CHR_PROPS_NOTIFY); cfgTelemetry.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); cfgTelemetry.setFixedLen(sizeof(TelemetryPacket)); cfgTelemetry.begin(); cfgTelemetry.write((uint8_t*)&telem, sizeof(telem)); #endif #ifdef FEATURE_IMU_STREAM cfgImuStream.setProperties(CHR_PROPS_NOTIFY); cfgImuStream.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); cfgImuStream.setFixedLen(sizeof(ImuPacket)); cfgImuStream.setCccdWriteCallback(onImuStreamCccd); cfgImuStream.begin(); #endif Serial.print("[BLE] ATT_TABLE_SIZE="); Serial.print(ATT_TABLE_SIZE); Serial.print(" | chars=2"); #ifdef FEATURE_TELEMETRY Serial.print("+TELEM"); #endif #ifdef FEATURE_IMU_STREAM Serial.print("+STREAM"); #endif Serial.println(); } #endif // FEATURE_CONFIG_SERVICE // ─── Telemetry push ─────────────────────────────────────────────────────────── #ifdef FEATURE_TELEMETRY void pushTelemetry(unsigned long now) { telem.uptimeSeconds = now / 1000; telem.tempCelsius = cachedTempC; telem.biasRmsRadS = statBiasRms; telem.recalCount = statRecalCount; #ifdef FEATURE_TAP_DETECTION telem.leftClicks = statLeftClicks; telem.rightClicks = statRightClicks; #endif cfgTelemetry.write ((uint8_t*)&telem, sizeof(telem)); cfgTelemetry.notify((uint8_t*)&telem, sizeof(telem)); } #endif