Add persistent battery ADC calibration across boards

Add meter-reference calibration with saved ADC corrections, checked persistence and rollback. Preserve existing board coefficients and nRF52 voltage policy, and merge the Mini Repeater feature gates.

Validation: 69 focused tests, hosted documentation checks, and four firmware builds passed. Mini Repeater retains calibration and its existing filesystem with 376 flash bytes free; earlier Mini Sensor size failures remain documented.
This commit is contained in:
mikecarper committed 2026-10-22 18:56:32 -07:00
1 parent 94a3b7e568
commit ab6412e73c
108 files changed
+1303 -83

No files matched your search

+152
View File
@@ -0,0 +1,152 @@
#include "BatteryCalibrationStore.h"
#include "BatteryCalibration.h"
#include "CheckedSettingsFile.h"
#include "CLICommandUtils.h"
#include "PersistentStoreFormat.h"
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#include "AtomicFileWriter.h"
#else
#include "ContactFileTransaction.h"
#endif
#if defined(NRF52_POWER_MANAGEMENT)
#include "NRF52VoltagePolicy.h"
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
namespace mesh { namespace power {
namespace {
FILESYSTEM* calibration_fs = nullptr;
const char* const calibration_path = "/battery_cal";
bool usesVoltagePolicy() {
#if defined(NRF52_POWER_MANAGEMENT)
return voltagePolicySupported();
#else
return false;
#endif
}
void encode(float multiplier, uint8_t (&data)[12]) {
memcpy(data, "ADC1", 4);
static_assert(sizeof(multiplier) == sizeof(uint32_t), "ADC record needs 32-bit float");
uint32_t bits;
memcpy(&bits, &multiplier, sizeof(bits));
storage::writeLE32(data + 4, bits);
storage::writeLE32(data + 8, storage::updateCRC32(0xffffffffUL, data, 8));
}
bool readMultiplier(float& multiplier) {
uint8_t data[12];
if (!storage::readFixedSettingsFile(calibration_fs, calibration_path, data, sizeof(data))
|| memcmp(data, "ADC1", 4) != 0
|| storage::readLE32(data + 8) != storage::updateCRC32(0xffffffffUL, data, 8)) return false;
const uint32_t bits = storage::readLE32(data + 4);
memcpy(&multiplier, &bits, sizeof(bits));
return isfinite(multiplier) && multiplier >= 0.0f && multiplier <= 10.0f;
}
bool saveMultiplier(float multiplier) {
if (!calibration_fs) return false;
uint8_t data[12], verified[12];
encode(multiplier, data);
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
AtomicFileWriter file(calibration_fs, calibration_path);
#else
ContactFileTransaction file(calibration_fs, calibration_path);
#endif
if (!file || file.write(data, sizeof(data)) != sizeof(data) || !file.commit()) return false;
return storage::readFixedSettingsFile(calibration_fs, calibration_path,
verified, sizeof(verified))
&& memcmp(data, verified, sizeof(data)) == 0;
}
} // namespace
bool beginBatteryCalibrationStore(MainBoard& board, FILESYSTEM* fs) {
calibration_fs = fs;
if (usesVoltagePolicy() || board.getAdcMultiplier() <= 0.0f) return true;
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
if (!fs || !ContactFileTransaction::recover(fs, calibration_path)) return false;
#endif
if (!fs) return false;
if (!fs->exists(calibration_path)) return true;
float multiplier;
return readMultiplier(multiplier) && board.setAdcMultiplier(multiplier);
}
bool setStoredAdcMultiplier(MainBoard& board, float multiplier,
char* reply, size_t capacity) {
if (!isfinite(multiplier) || multiplier < 0.0f || multiplier > 10.0f) {
snprintf(reply, capacity, "Error: multiplier must be 0-10");
return false;
}
if (board.getAdcMultiplier() <= 0.0f) {
snprintf(reply, capacity, "Error: ADC calibration unsupported");
return false;
}
#if defined(NRF52_POWER_MANAGEMENT)
if (usesVoltagePolicy()) {
char command[48];
snprintf(command, sizeof(command), "set adc.multiplier %.3f", multiplier);
return handleVoltagePolicyCommand(command, reply, capacity)
&& strncmp(reply, "OK", 2) == 0;
}
#endif
const float previous = board.getAdcMultiplier();
if (!board.setAdcMultiplier(multiplier)) {
snprintf(reply, capacity, "Error: multiplier rejected by board");
return false;
}
if (!saveMultiplier(multiplier)) {
const bool restored = board.setAdcMultiplier(previous);
snprintf(reply, capacity, "%s", restored
? "Error: save unconfirmed; reboot may differ"
: "Error: save unconfirmed; runtime restore failed; reboot may differ");
return false;
}
if (multiplier == 0.0f) snprintf(reply, capacity, "OK - using default board multiplier");
else snprintf(reply, capacity, "OK - multiplier set to %.3f (saved)", multiplier);
return true;
}
bool handleStoredAdcMultiplierCommand(MainBoard& board, const char* command,
char* reply, size_t capacity) {
if (!command || !reply || !capacity) return false;
const bool get = strncmp(command, "get ", 4) == 0;
const bool set = strncmp(command, "set ", 4) == 0;
if (!get && !set) return false;
const char* key = command + 4;
if (strncmp(key, "adc.multiplier", 14) != 0
|| (key[14] && key[14] != ' ' && key[14] != '\t')) return false;
if (board.getAdcMultiplier() <= 0.0f) {
snprintf(reply, capacity, "Error: ADC calibration unsupported");
return true;
}
#if defined(NRF52_POWER_MANAGEMENT)
if (usesVoltagePolicy()) return handleVoltagePolicyCommand(command, reply, capacity);
#endif
const char* value = key + 14;
while (*value == ' ' || *value == '\t') ++value;
if (get) {
if (*value) snprintf(reply, capacity, "Error: usage get adc.multiplier");
else if (board.getAdcMultiplier() <= 0.0f) snprintf(reply, capacity, "Error: ADC calibration unsupported");
else snprintf(reply, capacity, "> %.3f", board.getAdcMultiplier());
} else {
float multiplier;
if (!cli::parseDecimalStrict(value, multiplier))
snprintf(reply, capacity, "Error: invalid multiplier");
else setStoredAdcMultiplier(board, multiplier, reply, capacity);
}
return true;
}
bool handleStoredBatteryCalibrationCommand(MainBoard& board, const char* command,
char* reply, size_t capacity) {
if (handleBatteryCalibrationCommand(board, command, reply, capacity,
[&board](float multiplier, char* result, size_t result_capacity) {
return setStoredAdcMultiplier(board, multiplier, result, result_capacity);
})) return true;
return handleStoredAdcMultiplierCommand(board, command, reply, capacity);
}
}} // namespace mesh::power