219 lines
7.2 KiB
C++
219 lines
7.2 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_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
|