Files
air-mouse/source/ble_config.cpp
2026-03-03 11:44:38 +01:00

227 lines
9.1 KiB
C++

#include "ble_config.h"
#include "tap.h"
#include "battery.h"
#include "buttons.h"
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>
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_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() {
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;
b.btnLeftPin = cfg.btnLeftPin;
b.btnRightPin = cfg.btnRightPin;
b.btnMiddlePin = cfg.btnMiddlePin;
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 <= 2) { 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);
// btnXPin: accept BTN_PIN_NONE (0xFF) or a valid Arduino pin number (0-10 = D0-D10)
cfg.btnLeftPin = (b->btnLeftPin <= 10 || b->btnLeftPin == BTN_PIN_NONE) ? b->btnLeftPin : BTN_PIN_NONE;
cfg.btnRightPin = (b->btnRightPin <= 10 || b->btnRightPin == BTN_PIN_NONE) ? b->btnRightPin : BTN_PIN_NONE;
cfg.btnMiddlePin = (b->btnMiddlePin <= 10 || b->btnMiddlePin == BTN_PIN_NONE) ? b->btnMiddlePin : BTN_PIN_NONE;
#ifdef FEATURE_PHYSICAL_BUTTONS
setupPhysicalButtons(); // reconfigure pins immediately (no restart needed)
#endif
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_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();
// 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