182 lines
6.6 KiB
C++
182 lines
6.6 KiB
C++
#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>
|
|
|
|
#if BATTERY_CALIBRATION_VOLTAGE_POLICY_ONLY && !defined(NRF52_POWER_MANAGEMENT)
|
|
#error "Policy-only battery calibration requires NRF52_POWER_MANAGEMENT"
|
|
#endif
|
|
|
|
namespace mesh { namespace power {
|
|
namespace {
|
|
#if !BATTERY_CALIBRATION_VOLTAGE_POLICY_ONLY
|
|
FILESYSTEM* calibration_fs = nullptr;
|
|
const char* const calibration_path = "/battery_cal";
|
|
#endif
|
|
|
|
bool usesVoltagePolicy() {
|
|
#if defined(NRF52_POWER_MANAGEMENT)
|
|
return voltagePolicySupported();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#if !BATTERY_CALIBRATION_VOLTAGE_POLICY_ONLY
|
|
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];
|
|
encode(multiplier, data);
|
|
#if defined(NRF52_PLATFORM)
|
|
return storage::writeCheckedSettingsFile(calibration_fs, calibration_path,
|
|
data, sizeof(data)) == AtomicFileWriter::Outcome::Committed;
|
|
#else
|
|
uint8_t verified[12];
|
|
#if 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;
|
|
#endif
|
|
}
|
|
#endif
|
|
} // namespace
|
|
|
|
bool beginBatteryCalibrationStore(MainBoard& board, FILESYSTEM* fs) {
|
|
#if BATTERY_CALIBRATION_VOLTAGE_POLICY_ONLY
|
|
// A tagged board must finish its boot policy initialization first. Even a
|
|
// missing/corrupt policy file initializes safe defaults; never silently
|
|
// select a second calibration store if the board lifecycle is incomplete.
|
|
(void)board;
|
|
(void)fs;
|
|
return usesVoltagePolicy();
|
|
#else
|
|
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);
|
|
#endif
|
|
}
|
|
|
|
bool setStoredAdcMultiplier(MainBoard& board, float multiplier,
|
|
char* reply, size_t capacity) {
|
|
if (!isfinite(multiplier) || multiplier < 0.0f || multiplier > 10.0f) {
|
|
mesh::cli::formatError(reply, capacity, "multiplier 0-10");
|
|
return false;
|
|
}
|
|
if (board.getAdcMultiplier() <= 0.0f) {
|
|
mesh::cli::formatError(reply, capacity, "unsupported");
|
|
return false;
|
|
}
|
|
#if defined(NRF52_POWER_MANAGEMENT)
|
|
if (usesVoltagePolicy()) {
|
|
return setConfiguredAdcMultiplier(multiplier, reply, capacity);
|
|
}
|
|
#endif
|
|
#if BATTERY_CALIBRATION_VOLTAGE_POLICY_ONLY
|
|
mesh::cli::formatError(reply, capacity, "voltage policy unsupported");
|
|
return false;
|
|
#else
|
|
const float previous = board.getAdcMultiplier();
|
|
if (!board.setAdcMultiplier(multiplier)) {
|
|
mesh::cli::formatError(reply, capacity, "invalid multiplier");
|
|
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 - defaults (saved)");
|
|
else snprintf(reply, capacity, "OK - ADC %.3f (saved)", multiplier);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
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) {
|
|
mesh::cli::formatError(reply, capacity, "unsupported");
|
|
return true;
|
|
}
|
|
#if BATTERY_CALIBRATION_VOLTAGE_POLICY_ONLY
|
|
return handleVoltagePolicyCommand(command, reply, capacity);
|
|
#else
|
|
#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) mesh::cli::formatError(reply, capacity, "usage get adc.multiplier");
|
|
else if (board.getAdcMultiplier() <= 0.0f) mesh::cli::formatError(reply, capacity, "unsupported");
|
|
else snprintf(reply, capacity, "> %.3f", board.getAdcMultiplier());
|
|
} else {
|
|
float multiplier;
|
|
if (!cli::parseDecimalStrict(value, multiplier))
|
|
mesh::cli::formatError(reply, capacity, "invalid multiplier");
|
|
else setStoredAdcMultiplier(board, multiplier, reply, capacity);
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
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
|