Files
air-mouse/source/ble_config.cpp
2026-03-01 18:24:12 +01:00

170 lines
6.7 KiB
C++

#include "ble_config.h"
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>
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