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:
1 parent
94a3b7e568
commit
ab6412e73c
108 files changed
+1303
-83
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user