#include "ble_config.h" #include "tap.h" #include "battery.h" #include "buttons.h" #include #include using namespace Adafruit_LittleFS_Namespace; extern File cfgFile; // BLE Config Service objects #ifndef GIT_HASH #define GIT_HASH "unknown" #endif #ifdef FEATURE_CONFIG_SERVICE BLEService cfgService (0x1234); BLECharacteristic cfgBlob (0x1235); // ConfigBlob R/W 20 bytes BLECharacteristic cfgCommand (0x1236); // Command W 1 byte BLECharacteristic cfgGitHash (0x1239); // GitHash R 8 bytes (7-char hash + NUL) #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_SLOW: pinMode(PIN_HICHG, INPUT); break; case CHARGE_FAST: pinMode(PIN_HICHG, OUTPUT); digitalWrite(PIN_HICHG, LOW); break; } const char* n[] = {"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() { unsigned long t0 = millis(); InternalFS.remove(CONFIG_FILENAME); cfgFile.open(CONFIG_FILENAME, FILE_O_WRITE); if (cfgFile) { cfgFile.write((uint8_t*)&cfg, sizeof(cfg)); cfgFile.close(); } unsigned long elapsed = millis() - t0; if (elapsed > 5) { Serial.print("[CFG] Saved ("); Serial.print(elapsed); Serial.println("ms - flash block)"); } else { Serial.println("[CFG] Saved"); } } // 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.tapThreshold = cfg.tapThreshold; b.tapAction = (uint8_t)cfg.tapAction; b.tapKey = cfg.tapKey; b.tapMod = cfg.tapMod; b.tapFreezeEnabled = cfg.tapFreezeEnabled; b.jerkThreshold = cfg.jerkThreshold; b.featureFlags = cfg.featureFlags; cfgBlob.write((uint8_t*)&b, sizeof(b)); } #endif void factoryReset() { cfg = CFG_DEFAULTS; saveConfig(); applyChargeMode(cfg.chargeMode); #ifdef FEATURE_TAP_DETECTION applyTapThreshold(); #endif #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 <= 1) { cfg.chargeMode = (ChargeMode)b->chargeMode; applyChargeMode(cfg.chargeMode); } #ifdef FEATURE_TAP_DETECTION if (b->tapThreshold >= 1 && b->tapThreshold <= 31) { cfg.tapThreshold = b->tapThreshold; applyTapThreshold(); } if (b->tapAction <= 3) cfg.tapAction = (TapAction)b->tapAction; cfg.tapKey = b->tapKey; cfg.tapMod = b->tapMod; #endif cfg.tapFreezeEnabled = b->tapFreezeEnabled ? 1 : 0; if (b->jerkThreshold >= 100.0f && b->jerkThreshold <= 50000.0f) cfg.jerkThreshold = b->jerkThreshold; cfg.featureFlags = b->featureFlags & (FLAG_TAP_ENABLED | FLAG_TEMP_COMP_ENABLED | FLAG_AUTO_RECAL_ENABLED); saveConfig(); Serial.print("[CFG] Written - sens="); Serial.print(cfg.sensitivity,0); Serial.print(" dz="); Serial.print(cfg.deadZone,3); Serial.print(" tapThr="); Serial.print(cfg.tapThreshold); Serial.print(" tapAction="); Serial.println(cfg.tapAction); } 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_OTA if (d[0] == 0x02) pendingOTA = true; #endif } #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 | CHR_PROPS_WRITE_WO_RESP); cfgCommand.setPermission(SECMODE_OPEN, SECMODE_OPEN); cfgCommand.setFixedLen(1); cfgCommand.setWriteCallback(onCommandWrite); cfgCommand.begin(); // Git hash - 8-byte fixed field (7 hex chars + NUL), read-only cfgGitHash.setProperties(CHR_PROPS_READ); cfgGitHash.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); cfgGitHash.setFixedLen(8); cfgGitHash.begin(); { uint8_t buf[8] = {}; strncpy((char*)buf, GIT_HASH, 7); cfgGitHash.write(buf, 8); } Serial.print("[BUILD] git="); Serial.println(GIT_HASH); #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 #ifdef FEATURE_BATTERY_MONITOR telem.battVoltage = readBatteryVoltage(); #endif cfgTelemetry.write ((uint8_t*)&telem, sizeof(telem)); cfgTelemetry.notify((uint8_t*)&telem, sizeof(telem)); } #endif