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

+2
View File
@@ -475,6 +475,8 @@ jobs:
python3 -B test/test_regulator_recovery.py -v
python3 -B test/test_regulator_settings.py -v
python3 -B test/test_battery_forwarding.py -v
python3 -B test/test_battery_calibration.py -v
python3 -B test/test_battery_calibration_store.py -v
python3 -B test/test_power_saving_diagnostics.py -v
python3 -B test/test_power_saving_diagnostics_buckets.py -v
python3 -B test/test_power_saving_role_diagnostics.py -v
+49 -3
View File
@@ -2526,6 +2526,49 @@ sessions. Setting the password to `off` makes it the literal password `off`.
---
#### Calibrate the battery reading against a meter
**Availability:** Repeater, Room Server, Sensor, Companion and Terminal Chat
firmware on boards with a supported battery ADC. Use an administrative CLI
connection; infrastructure roles also accept authenticated remote commands.
```text
battery calibrate 4.18
get adc.multiplier
battery calibrate reset
```
Measure the battery voltage with a voltmeter and enter that reading in volts.
The firmware calculates `new multiplier = current multiplier * meter voltage /
reported voltage`, applies it and saves it for restart. A reference is required;
the command does not assume a chemistry or a 4.2 V full battery. Enter the voltage
of the battery input the board actually measures, including pack voltage where
that input supports a pack. For example, `battery calibrate 13.2` supplies a
13.200 V reference; it does not change the board's measurement range.
Calibration supports the ESP32, nRF52, RP2040 and STM32 boards with implemented
battery-voltage ADC conversions, including the Wio Tracker L2's external ADS1115.
An unsupported board or unavailable/saturated reading returns an error. Digital
PMU/fuel-gauge and battery-module readings retain their hardware scaling; boards
without a battery-voltage input cannot be calibrated through this command.
The meter reference must be within 50% of the reported voltage. Newly supported
ADCs accept a relative multiplier from `0.500` to `1.500`, where `1.000` retains
their original conversion. Existing board-specific absolute multipliers and
saved values keep their established meaning. The five legacy absolute drivers
are Heltec V4, V4 R8, RC32, Meshnology W12 and ProMicro.
`battery calibrate reset` restores and saves the board's default conversion.
`set adc.multiplier 0` also resets it. A successful numeric calibration reply shows the effective
multiplier, including any rounding by the board. A failed save reports an error
and attempts to restore the previous runtime setting.
Calibration corrects voltage reporting and the battery percentages/protection
that use that reading. On nRF52 boards with voltage policy it also applies to
the boot reading and running cutoff. It does not adjust charger hardware or
create a current measurement. This is a single-point scale correction, not an
ADC offset or nonlinear calibration.
#### Fine-tune the battery reading
**Usage:**
- `get adc.multiplier`
@@ -2536,13 +2579,16 @@ sessions. Setting the password to `off` makes it the literal password `off`.
**Default:** `0.0` (value defined by board)
**Note:** On nRF52 boards with power management and a boot-voltage setting,
**Note:** On newly supported ADC boards this is a relative `0.500`-`1.500`
correction, with `0` restoring the default. Existing absolute-multiplier boards
retain their coefficient units and accept `0` or a positive value up to `10`.
On nRF52 boards with power management and a boot-voltage setting,
this is a saved *relative* calibration: `1.000` keeps the board conversion,
`1.050` raises all reported battery millivolts by 5%, and `0` resets to
`1.000`. The allowed range is `0.500`-`1.500`. This affects boot protection,
running cutoff, telemetry, battery alerts, and percentage. Calibrate against a
voltmeter before enabling a low cutoff. Other boards retain their existing
board-specific multiplier behavior, or return unsupported.
voltmeter before enabling a low cutoff. Boards without a supported ADC return
unsupported.
---
@@ -0,0 +1,119 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/test-results/battery_calibration_2026-10-22/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# Shared battery ADC calibration validation - 2026-10-22
Final integration baseline: `94a3b7e568781533510721670af24c162804a5ca` on
`keymindCascade`, plus the calibration changes. Earlier builds and link
failures below used `af106a0ee9edd8ee0113b7b364290e0944245b86`; the final
integration section supersedes those results for the targets it names.
These checks do not constitute an installed-device or voltmeter acceptance test.
## Behavior and compatibility
- `battery calibrate <measured volts>` calculates the correction from the
effective existing multiplier and the reported battery voltage.
- A reference is mandatory, with millivolt precision and no assumed battery
chemistry or full voltage. A single correction must lie within 50% of the
reported reading. Zero and saturated readings are rejected.
- `battery calibrate reset` saves the board's default conversion.
- Real ADC backends across ESP32, nRF52, RP2040 and STM32 opt into calibration.
The Wio Tracker L2's external ADS1115 is included. Boards requiring an external
divider still require that hardware; software does not add an input.
- Existing absolute coefficients for Heltec V4, V4 R8, RC32, Meshnology W12 and
ProMicro retain their units and saved values. Other supported ADCs use a
relative correction of 0.500-1.500, with zero restoring their factory scale.
- The nRF52 boot-voltage policy remains authoritative where available, and is
applied once. Without that policy, ADC commands use the shared correction.
- Infrastructure roles restore their existing calibration preference centrally,
including Repeater. Companion and Terminal use a checked, versioned
`/battery_cal` record without changing their existing preference layouts.
- Failed saves attempt to restore the runtime multiplier. An uncertain
publication returns an error rather than claiming persistence. Volatile
recovery storage is not used for saving calibration.
- PMU/fuel-gauge readings, UART battery-module readings, raw-count test
placeholders and boards with no battery-voltage measurement remain excluded.
## Host checks
All following checks passed using `TMPDIR=/var/tmp` (the machine's `/tmp` user
quota prevented compiler temporary-file creation):
| Check | Coverage |
| --- | --- |
| `test/test_battery_calibration.py` - 3 tests | Production parsing, meter correction, existing absolute coefficients, pack references, effective multiplier, reset, unsupported/zero/saturated readings, bounds, actual MainBoard scaling and nRF52 policy precedence. |
| `test/test_battery_calibration_store.py` - 1 test across four platform fixtures | ESP32, RP2040, nRF52 and STM32 transaction implementations; reboot restore, every single-bit record corruption, truncation, write failure, uncertain publication, reset, missing persistent storage and backup recovery. |
| `test/test_battery_forwarding.py` - 19 tests | Actual infrastructure setter, failed-save rollback, nRF52 policy fallback, and refreshed battery-based forwarding decisions. |
| `test/test_heltec_r8_external_power.py` - 1 test | Existing R8 power estimate still uses the calibrated voltage. |
| `test/test_stm32_companion_profile.py` - 21 tests | Existing ADC conversion and sampling remain exact at default calibration; memory/profile constraints retained. |
| `test/test_rp2040_application_capabilities.py` - 11 tests | Existing upload artifact and capability validation. |
The production calibration, store and forwarding harnesses use AddressSanitizer
and UndefinedBehaviorSanitizer. `git diff --check` also passed.
## Earlier firmware builds
Before integration, PlatformIO processes were run serially. Successful targets:
- `Heltec_v3_repeater`
- `Heltec_v3_terminal_chat`
- `Heltec_v3_companion_radio_usb`
- `RAK_3401_repeater`
- `RAK_3401_room_server`
- `RAK_3401_sensor`
- `RAK_3401_companion_radio_usb`
- `Xiao_rp2040_companion_radio_usb`
- `wio-e5-mini_companion_radio_usb`
That earlier Mini Companion occupied 228,832 of 229,376 application bytes:
544 bytes remained. This validates the current build recipe, not every possible version
string, optional feature combination or firmware target.
## Earlier STM32 flash blockers
The following targets compile but fail the linker flash limit. Both failures
were reproduced from an isolated, unchanged checkout of the baseline with the
same build recipes and compiler versions:
| Target | Baseline overflow | Calibration build overflow | Additional bytes |
| --- | ---: | ---: | ---: |
| `wio-e5-mini_repeater` | 1,168 | 2,852 | 1,684 |
| `wio-e5-mini_sensor` | 25,584 | 27,368 | 1,784 |
The calibration increase consists of code and constants; it did not introduce
additional floating-point runtime helpers in the Repeater. Filesystem and
application boundaries have not been moved, and no unrelated feature was
removed to conceal these build failures. These results apply to the earlier source baseline above, before the Mini
Repeater feature gates in `94a3b7e56`. The integration checks below supersede
that earlier Repeater result when a newer build succeeds.
## Final integration checks
After merging `94a3b7e56`, 69 distinct focused tests passed: 42 calibration,
platform-storage and related regressions, 19 battery-forwarding tests, and
8 Mini Repeater profile checks. The 23 calibration/store/forwarding tests
were rerun successfully after the final reply compaction. Both new calibration
suites are included in the unit-test workflow.
The first integrated Mini Repeater link exceeded flash by 432 bytes.
Disabling guessed branch probabilities in that target's existing size recipe,
sharing the existing save-failure replies, keeping the command key in constant
storage and shortening calibration replies recovered 808 bytes. Calibration,
routing, logging, 50 neighbours and the existing 16 KiB filesystem are retained.
No extra feature was disabled to make this image fit.
The final merged-source builds passed their flash and runtime-memory checks:
| Target | Application bytes used | Application limit | Free flash bytes |
| --- | ---: | ---: | ---: |
| `Heltec_v3_repeater` | 1,890,881 | 3,342,336 | 1,451,455 |
| `RAK_3401_repeater` | 559,616 | 815,104 | 255,488 |
| `wio-e5-mini_repeater` | 245,384 | 245,760 | 376 |
| `wio-e5-mini_companion_radio_usb` | 228,664 | 229,376 | 712 |
These small Mini margins qualify the stated recipes, not arbitrary optional
features or longer build metadata. The earlier Mini Sensor overflow remains
recorded above; that target was not rebuilt in this final integration campaign.
No physical ADC, voltmeter, charging or installed-device test was performed.
+1
View File
@@ -75,6 +75,7 @@ Completed validation reports, experiments, and test guides are collected here. O
## Build, storage, memory, and checklists
- [Shared battery ADC calibration validation, 2026-10-22](battery_calibration_2026-10-22.md)
- [Heltec V2 BLE RAM reserve repair, 2026-10-22](heltec_v2_ble_memory_2026-10-22.md)
- [ESP32 Bluetooth shutdown and radio2 CPU idle, 2026-10-21](esp32_ble_radio2_sleep_2026-10-21.md)
- [RAK3401 all firmware CPU sleep checks, 2026-10-21](rak3401_all_roles_cpu_sleep_2026-10-21.md)
+12
View File
@@ -9,6 +9,7 @@
#include <helpers/ui/OLEDControllerSettings.h>
#endif
#include <helpers/BatteryChargeCLI.h>
#include <helpers/BatteryCalibrationStore.h>
#include <helpers/MorseCLI.h>
#include <helpers/MorseLoopPolicy.h>
#if defined(MESH_HIL_TX_FREQ_MHZ)
@@ -3026,6 +3027,13 @@ void MyMesh::begin(bool has_display, bool radio_available,
// load persisted prefs
const bool prefs_ready =
_store->loadPrefs(_prefs, sensors.node_lat, sensors.node_lon);
FILESYSTEM* battery_calibration_fs = _store->getPrimaryFS();
#if defined(NRF52_PLATFORM)
if (_store->isVolatilePrimaryFS()) battery_calibration_fs = nullptr;
#endif
if (!mesh::power::beginBatteryCalibrationStore(board, battery_calibration_fs)) {
MESH_DEBUG_PRINTLN("Battery calibration could not be restored; using board default");
}
#if defined(ESP32_PLATFORM) && defined(WIFI_SSID)
#ifdef ADMIN_PASSWORD
const char* management_default = ADMIN_PASSWORD;
@@ -10637,6 +10645,8 @@ static bool isCompanionRadioPrefsCommand(const char* command) {
// Called only for directly attached USB/BLE/TCP/browser clients. Commands
// received over LoRa use handleCommand() with a nonzero sender timestamp.
bool MyMesh::handleDirectCommand(const char* command, char* reply, size_t reply_size) {
if (mesh::power::handleStoredBatteryCalibrationCommand(
board, command, reply, reply_size)) return true;
#if MESH_ENABLE_LOST_REPLY
if (mesh::companion::handleLostReplyCommand(
_prefs, command, reply, reply_size, [this]() { return savePrefs(); })) return true;
@@ -10834,6 +10844,8 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp,
&& mesh::handleReaderReply(command, reply, reply_capacity)) return true;
#endif
if (mesh::power::handleStoredBatteryCalibrationCommand(
board, command, reply, reply_capacity)) return true;
#if defined(NRF52_POWER_MANAGEMENT)
if (mesh::power::handleVoltagePolicyCommand(command, reply,
reply_capacity)) return true;
-1
View File
@@ -2692,7 +2692,6 @@ void MyMesh::begin(FILESYSTEM *fs) {
updateAdvertTimer();
updateFloodAdvertTimer();
board.setAdcMultiplier(_prefs.adc_multiplier);
#if ENV_INCLUDE_GPS == 1
applyGpsPrefs();
+11
View File
@@ -1,5 +1,6 @@
#include <helpers/RadioProfileCLI.h>
#include <helpers/BatteryChargeCLI.h>
#include <helpers/BatteryCalibrationStore.h>
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#if MESH_PACKET_LOGGING
@@ -424,6 +425,10 @@ public:
mesh::setUsbLoggingEnabled(_prefs.usb_logging_enabled != 0);
#endif
if (!mesh::power::beginBatteryCalibrationStore(board, volatile_primary_fs ? nullptr : _fs)) {
MESH_DEBUG_PRINTLN("Battery calibration could not be restored; using board default");
}
loadContacts();
_public = addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
}
@@ -470,6 +475,12 @@ public:
}
void handleCommand(const char* command) {
char calibration_reply[160];
if (mesh::power::handleStoredBatteryCalibrationCommand(board, command,
calibration_reply, sizeof(calibration_reply))) {
Serial.println(calibration_reply);
return;
}
#if MESH_BATTERY_CHARGE_CONTROL
char charge_reply[160];
if (mesh::power::handleBatteryChargeCommand(board, command, charge_reply,
-1
View File
@@ -1316,7 +1316,6 @@ void SensorMesh::begin(FILESYSTEM* fs) {
updateAdvertTimer();
updateFloodAdvertTimer();
board.setAdcMultiplier(_prefs.adc_multiplier);
#if ENV_INCLUDE_GPS == 1
applyGpsPrefs();
+18 -2
View File
@@ -5,6 +5,7 @@
#include <math.h>
#include "helpers/UsbLogging.h"
#include "helpers/BatteryChargeSupport.h"
#include "helpers/BatteryCalibration.h"
#include "helpers/RegulatorMode.h"
#include "helpers/PowerSavingDiagnostics.h"
#include "helpers/LedPolicy.h"
@@ -137,13 +138,28 @@ namespace mesh {
class MainBoard {
bool _radio_test_active = false;
float _battery_calibration_factor = 1.0f;
protected:
uint16_t calibrateBatteryMillivolts(uint16_t raw_mv) const {
return mesh::power::scaleBatteryMillivolts(raw_mv, _battery_calibration_factor);
}
public:
void setRadioTestActive(bool active) { _radio_test_active = active; }
bool isRadioTestActive() const { return _radio_test_active; }
virtual uint16_t getBattMilliVolts() = 0;
virtual float getMCUTemperature() { return NAN; }
virtual bool setAdcMultiplier(float multiplier) { return false; };
virtual float getAdcMultiplier() const { return 0.0f; }
virtual bool supportsBatteryCalibration() const { return false; }
// Relative to the board's built-in ADC conversion; zero restores its scale.
// Boards with an existing absolute ADC coefficient retain their overrides.
virtual bool setAdcMultiplier(float multiplier) {
if (!supportsBatteryCalibration()
|| !mesh::power::validRelativeBatteryMultiplier(multiplier)) return false;
_battery_calibration_factor = multiplier == 0.0f ? 1.0f : multiplier;
return true;
}
virtual float getAdcMultiplier() const {
return supportsBatteryCalibration() ? _battery_calibration_factor : 0.0f;
}
virtual const char* getManufacturerName() const = 0;
virtual void onBeforeTransmit() { }
virtual void onAfterTransmit() { }
+118
View File
@@ -0,0 +1,118 @@
#pragma once
#include <math.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
namespace mesh { namespace power {
// New ADC backends use a correction relative to their original conversion.
// Existing board-specific absolute multipliers retain their own units.
inline bool validRelativeBatteryMultiplier(float multiplier) {
return isfinite(multiplier) && (multiplier == 0.0f
|| (multiplier >= 0.5f && multiplier <= 1.5f));
}
inline uint16_t scaleBatteryMillivolts(uint16_t millivolts, float multiplier) {
if (millivolts == 0 || !isfinite(multiplier) || multiplier <= 0.0f)
return millivolts;
const float scaled = millivolts * multiplier;
if (scaled >= 65535.0f) return UINT16_MAX;
return static_cast<uint16_t>(scaled + 0.5f);
}
// Volts with millivolt precision. Require a measured value; neither chemistry
// nor charger regulation voltage supplies a calibration reference.
inline bool parseBatteryReferenceMillivolts(const char* text, uint16_t& result) {
if (!text || *text < '0' || *text > '9') return false;
uint32_t whole = 0, fraction = 0;
while (*text >= '0' && *text <= '9') {
whole = whole * 10 + static_cast<unsigned>(*text++ - '0');
if (whole > 65) return false;
}
unsigned digits = 0;
if (*text == '.') {
++text;
while (*text >= '0' && *text <= '9') {
if (++digits > 3) return false;
fraction = fraction * 10 + static_cast<unsigned>(*text++ - '0');
}
if (digits == 0) return false;
}
while (*text == ' ' || *text == '\t') ++text;
if (*text) return false;
while (digits++ < 3) fraction *= 10;
const uint32_t millivolts = whole * 1000 + fraction;
if (millivolts == 0 || millivolts > UINT16_MAX) return false;
result = static_cast<uint16_t>(millivolts);
return true;
}
// Setter owns application, checked persistence and rollback. It leaves a
// useful error in reply when it fails. This keeps nRF52 boot-voltage policy
// and legacy absolute multiplier storage authoritative in their own roles.
template <typename Board, typename Setter>
bool handleBatteryCalibrationCommand(Board& board, const char* command,
char* reply, size_t capacity,
Setter set_multiplier) {
if (!command || !reply || capacity == 0) return false;
while (*command == ' ' || *command == '\t') ++command;
static const char key[] = "battery calibrate";
const size_t length = sizeof(key) - 1;
if (strncmp(command, key, length) != 0
|| (command[length] && command[length] != ' ' && command[length] != '\t'))
return false;
const char* value = command + length;
while (*value == ' ' || *value == '\t') ++value;
const float previous = board.getAdcMultiplier();
if (!isfinite(previous) || previous <= 0.0f) {
snprintf(reply, capacity, "Error: battery ADC unsupported");
return true;
}
const char* reset_end = value;
if (strncmp(value, "reset", 5) == 0) {
reset_end = value + 5;
while (*reset_end == ' ' || *reset_end == '\t') ++reset_end;
if (*reset_end == 0) {
*reply = 0;
if (set_multiplier(0.0f, reply, capacity))
snprintf(reply, capacity, "OK - ADC defaults restored (saved)");
else if (*reply == 0)
snprintf(reply, capacity, "Error: calibration save failed");
return true;
}
}
uint16_t reference = 0;
if (!parseBatteryReferenceMillivolts(value, reference)) {
snprintf(reply, capacity, "Error: use battery calibrate <volts>|reset");
return true;
}
const uint16_t reported = board.getBattMilliVolts();
if (reported == 0 || reported == UINT16_MAX) {
snprintf(reply, capacity, "Error: battery voltage unavailable");
return true;
}
const float correction = static_cast<float>(reference) / reported;
if (!validRelativeBatteryMultiplier(correction)) {
snprintf(reply, capacity, "Error: reference must be 50%%-150%% of reading");
return true;
}
const float multiplier = previous * correction;
if (!isfinite(multiplier) || multiplier <= 0.0f) {
snprintf(reply, capacity, "Error: invalid calibration correction");
return true;
}
*reply = 0;
if (set_multiplier(multiplier, reply, capacity)) {
snprintf(reply, capacity, "OK - %u.%03u V; multiplier %.6f (saved)",
static_cast<unsigned>(reference / 1000),
static_cast<unsigned>(reference % 1000), board.getAdcMultiplier());
} else if (*reply == 0) {
snprintf(reply, capacity, "Error: calibration save failed");
}
return true;
}
}} // namespace mesh::power
+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
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <MeshCore.h>
#include <helpers/IdentityStore.h>
namespace mesh { namespace power {
// Companion and Terminal Chat keep calibration outside their published
// preference layouts. Infrastructure roles retain their existing ADC field.
bool beginBatteryCalibrationStore(MainBoard& board, FILESYSTEM* fs);
bool setStoredAdcMultiplier(MainBoard& board, float multiplier,
char* reply, size_t capacity);
bool handleStoredAdcMultiplierCommand(MainBoard& board, const char* command,
char* reply, size_t capacity);
bool handleStoredBatteryCalibrationCommand(MainBoard& board, const char* command,
char* reply, size_t capacity);
}} // namespace mesh::power
+72 -17
View File
@@ -22,6 +22,7 @@
#include <helpers/ui/DisplayRotationSettings.h>
#endif
#include <helpers/BatteryChargeCLI.h>
#include <helpers/BatteryCalibration.h>
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
#include <helpers/MorseCLI.h>
#endif
@@ -1069,6 +1070,12 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
if (loaded) syncOtaConfigFromPrefs(); // persisted OTA policy/keys -> OtaContext (else keep safe defaults)
#endif
mesh::hilStartupTrace("prefs_apply_begin");
#if defined(NRF52_POWER_MANAGEMENT)
// The startup voltage policy owns the relative calibration on these boards.
// Its checked record must not be replaced by the legacy infrastructure field.
if (!mesh::power::voltagePolicySupported())
#endif
_board->setAdcMultiplier(_prefs->adc_multiplier);
#if defined(RAK_4631)
// Boot-time only. Later CLI saves must not cut a live peripheral/flash rail.
_sensors->configureSharedPeripheralMode(_prefs->peripherals_shared != 0);
@@ -3018,6 +3025,47 @@ bool CommonCLI::saveCommonPrefs() {
return _common_save_result_known && _common_save_succeeded;
}
bool CommonCLI::applyAndSaveAdcMultiplier(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: unsupported");
return false;
}
#if defined(NRF52_POWER_MANAGEMENT)
if (mesh::power::voltagePolicySupported()) {
char command[48];
snprintf(command, sizeof(command), "set adc.multiplier %.3f", multiplier);
const bool saved = mesh::power::handleVoltagePolicyCommand(command, reply, capacity)
&& strncmp(reply, "OK", 2) == 0;
if (saved) loopBatteryForwarding(true);
return saved;
}
#endif
const float previous = _board->getAdcMultiplier();
const float previous_pref = _prefs->adc_multiplier;
if (!_board->setAdcMultiplier(multiplier)) {
snprintf(reply, capacity, "Error: multiplier rejected by board");
return false;
}
_prefs->adc_multiplier = multiplier;
if (!trySavePrefs()) {
_prefs->adc_multiplier = previous_pref;
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;
}
loopBatteryForwarding(true);
if (multiplier == 0.0f) snprintf(reply, capacity, "OK - using default board multiplier");
else snprintf(reply, capacity, "OK - multiplier set to %.3f", multiplier);
return true;
}
bool CommonCLI::savePrimaryRadioParams(float freq, float bw, uint8_t sf,
uint8_t cr, uint16_t preamble) {
if (!isfinite(freq) || !isfinite(bw) || freq < 150.0f || freq > 2500.0f
@@ -3264,6 +3312,10 @@ bool CommonCLI::handleUDPBridgeCommand(uint32_t sender_timestamp,
void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
PrefsSaveReplyGuard save_reply(_prefs_save_failures, reply);
mesh::cli::normalizeCommandVerb(command);
if (mesh::power::handleBatteryCalibrationCommand(*_board, command, reply, 160,
[this](float multiplier, char* result, size_t capacity) {
return applyAndSaveAdcMultiplier(multiplier, result, capacity);
})) return;
#if MESH_ENABLE_BATTERY_FORWARDING
if (handleBatteryForwardingCommand(command, reply)) return;
#endif
@@ -4229,12 +4281,18 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
return;
}
#if defined(NRF52_POWER_MANAGEMENT)
if (mesh::power::handleVoltagePolicyCommand(command, reply, 160)) {
#if MESH_ENABLE_BATTERY_FORWARDING
if (strncmp(reply, "OK", 2) == 0) loopBatteryForwarding(true);
#endif
const bool adc_multiplier_command = strncmp(config, "adc.multiplier", 14) == 0
&& (config[14] == 0 || config[14] == ' ' || config[14] == '\t');
if (adc_multiplier_command && _board->getAdcMultiplier() <= 0.0f) {
strcpy(reply, "Error: unsupported");
return;
}
if (!adc_multiplier_command || mesh::power::voltagePolicySupported()) {
if (mesh::power::handleVoltagePolicyCommand(command, reply, 160)) {
if (strncmp(reply, "OK", 2) == 0) loopBatteryForwarding(true);
return;
}
}
#endif
// Infrastructure roles dispatch here rather than through CommonRadioPrefs.
if (strncmp(config, "path.hash.mode", 14) == 0
@@ -5721,19 +5779,8 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
float multiplier = 0.0f;
if (!mesh::cli::parseDecimalStrict(&config[15], multiplier)) {
strcpy(reply, "Error: invalid multiplier");
} else if (_board->setAdcMultiplier(multiplier)) {
_prefs->adc_multiplier = multiplier;
savePrefs();
#if MESH_ENABLE_BATTERY_FORWARDING
loopBatteryForwarding(true);
#endif
if (_prefs->adc_multiplier == 0.0f) {
strcpy(reply, "OK - using default board multiplier");
} else {
sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier);
}
} else {
strcpy(reply, "Error: unsupported");
applyAndSaveAdcMultiplier(multiplier, reply, 160);
};
} else if (memcmp(config, "reboot.interval ", 16) == 0) {
int hours = _atoi(&config[16]);
@@ -5804,7 +5851,15 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
return;
}
#if defined(NRF52_POWER_MANAGEMENT)
if (mesh::power::handleVoltagePolicyCommand(command, reply, 160)) return;
const bool adc_multiplier_command = strncmp(config, "adc.multiplier", 14) == 0
&& (config[14] == 0 || config[14] == ' ' || config[14] == '\t');
if (adc_multiplier_command && _board->getAdcMultiplier() <= 0.0f) {
strcpy(reply, "Error: unsupported");
return;
}
if (!adc_multiplier_command || mesh::power::voltagePolicySupported()) {
if (mesh::power::handleVoltagePolicyCommand(command, reply, 160)) return;
}
#endif
if (strcmp(config, "password") == 0) {
if (sender_timestamp == 0) {
+1
View File
@@ -945,6 +945,7 @@ class CommonCLI {
PrefsSaveRouting::Scope scope = PrefsSaveRouting::Scope::Common);
bool trySavePrefs(
PrefsSaveRouting::Scope scope = PrefsSaveRouting::Scope::Common);
bool applyAndSaveAdcMultiplier(float multiplier, char* reply, size_t capacity);
bool saveObserverPrefs();
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
#ifdef WITH_MQTT_BRIDGE
+9 -1
View File
@@ -518,6 +518,14 @@ public:
}
#endif
bool supportsBatteryCalibration() const override {
#ifdef PIN_VBAT_READ
return true;
#else
return false;
#endif
}
uint16_t getBattMilliVolts() override {
#ifdef PIN_VBAT_READ
analogReadResolution(12);
@@ -528,7 +536,7 @@ public:
}
raw = raw / 4;
return (2 * raw);
return calibrateBatteryMillivolts(2 * raw);
#else
return 0; // not supported
#endif
+16 -4
View File
@@ -293,12 +293,24 @@ void NRF52Board::loop() {
#endif
}
#ifdef NRF52_POWER_MANAGEMENT
uint16_t NRF52Board::calibrateBatteryMillivolts(uint16_t raw_mv) const {
return mesh::power::calibratedMillivolts(
raw_mv, mesh::power::configuredAdcPermille());
}
#ifdef NRF52_POWER_MANAGEMENT
if (supportsBatteryCalibration() && mesh::power::voltagePolicySupported()) {
return mesh::power::calibratedMillivolts(
raw_mv, mesh::power::configuredAdcPermille());
}
#endif
return mesh::MainBoard::calibrateBatteryMillivolts(raw_mv);
}
float NRF52Board::getAdcMultiplier() const {
#ifdef NRF52_POWER_MANAGEMENT
if (supportsBatteryCalibration() && mesh::power::voltagePolicySupported()) {
return mesh::power::configuredAdcPermille() / 1000.0f;
}
#endif
return mesh::MainBoard::getAdcMultiplier();
}
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 0
static void reloadWatchdogChannels() {
+3 -3
View File
@@ -102,10 +102,10 @@ public:
bool isUsbDataConnected() override;
bool isUsbHostConnected() override;
#ifdef NRF52_POWER_MANAGEMENT
// Relative to each board's built-in ADC conversion; 1.000 is unchanged.
// Power-managed boards use the early-boot voltage policy; other ADC boards
// use the shared relative multiplier persisted with their role settings.
uint16_t calibrateBatteryMillivolts(uint16_t raw_mv) const;
#endif
float getAdcMultiplier() const override;
void attachDynamicPrefs(KeyValueStore* prefs) { (void)prefs; } // no-op
+2
View File
@@ -162,6 +162,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return false; }
uint16_t getBattMilliVolts(){
if (!charge_pmu_ready) return 0;
#if defined(PORTABLE_MQTT_OBSERVER) && !defined(TBEAM_SUPREME_SX1262)
+2
View File
@@ -38,6 +38,8 @@ python3 test/test_companion_identity_startup.py # real saved/missing/unreadable
python3 test/test_startup_screen.py # early display ordering and cooperative entropy progress
python3 -B test/test_common_prefs_legacy_rxgain.py -v # compact stock gain, appended tails and truncation
python3 -B test/test_battery_forwarding.py -v # battery relay thresholds, hysteresis, calibration and checked settings records
python3 -B test/test_battery_calibration.py -v # meter references, ADC correction, reset and invalid-reading rejection
python3 -B test/test_battery_calibration_store.py -v # saved calibration, corruption and write-failure recovery across filesystems
python3 test/test_companion_settings_persistence_contract.py # Atomic settings and appended Bluetooth fields
python3 test/test_webconfig_ui.py # Web controls, reboot handling and generated-page consistency
python3 test/test_webconfig_ui_runtime.py # Real Chromium, including independent stealth toggle
+228
View File
@@ -0,0 +1,228 @@
"""Exercise production battery calibration parsing, scaling and command behavior."""
from pathlib import Path
import os
import subprocess
import tempfile
import unittest
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
HARNESS = r'''
#include <helpers/BatteryCalibration.h>
#include <cassert>
#include <cmath>
#include <cstring>
#include <limits>
#include <initializer_list>
using namespace mesh::power;
struct Board {
float multiplier = 1.0f;
uint16_t millivolts = 4000;
unsigned reads = 0;
float getAdcMultiplier() const { return multiplier; }
uint16_t getBattMilliVolts() { ++reads; return millivolts; }
};
int main() {
assert(validRelativeBatteryMultiplier(0));
assert(validRelativeBatteryMultiplier(0.5f));
assert(validRelativeBatteryMultiplier(1.5f));
assert(!validRelativeBatteryMultiplier(0.499f));
assert(!validRelativeBatteryMultiplier(1.501f));
assert(!validRelativeBatteryMultiplier(NAN));
assert(!validRelativeBatteryMultiplier(INFINITY));
assert(scaleBatteryMillivolts(4000, 1.05f) == 4200);
assert(scaleBatteryMillivolts(1001, 0.5f) == 501);
assert(scaleBatteryMillivolts(0, 1.5f) == 0);
assert(scaleBatteryMillivolts(60000, 1.5f) == UINT16_MAX);
assert(scaleBatteryMillivolts(4000, NAN) == 4000);
assert(scaleBatteryMillivolts(4000, 0) == 4000);
uint16_t parsed = 123;
assert(parseBatteryReferenceMillivolts("4.18", parsed) && parsed == 4180);
assert(parseBatteryReferenceMillivolts("13.200", parsed) && parsed == 13200);
assert(parseBatteryReferenceMillivolts("65.535\t ", parsed) && parsed == 65535);
assert(parseBatteryReferenceMillivolts("0.001", parsed) && parsed == 1);
for (const char* bad : {"", "0", "0.000", "-4.2", "+4.2", "nan", "inf",
"4e0", "4.2V", "4.2 extra", "4.", "4.2000", "65.536", "999999999999"}) {
parsed = 123;
assert(!parseBatteryReferenceMillivolts(bad, parsed) && parsed == 123);
}
Board board;
char reply[160] = {};
bool save_ok = true;
unsigned saves = 0;
float saved = -1;
auto setter = [&](float value, char* error, size_t capacity) {
++saves;
if (!save_ok) {
snprintf(error, capacity, "Error: simulated save failure");
return false;
}
saved = value;
board.multiplier = value == 0 ? 1 : value;
return true;
};
auto run = [&](const char* command) {
strcpy(reply, "untouched");
return handleBatteryCalibrationCommand(board, command, reply, sizeof(reply), setter);
};
assert(!run("battery calibrateX 4.18") && saves == 0);
assert(strcmp(reply, "untouched") == 0);
assert(run("battery calibrate 4.18") && saves == 1);
assert(fabs(saved - 1.045f) < 0.00001f);
assert(strstr(reply, "4.180 V") && strstr(reply, "(saved)"));
// Repeat calibration composes with the effective existing multiplier.
board.millivolts = 4180;
assert(run("battery calibrate 4.18") && saves == 2);
assert(fabs(saved - 1.045f) < 0.00001f);
// Works with an existing absolute board coefficient too.
board.multiplier = 5.42f;
board.millivolts = 4000;
assert(run("battery calibrate 4.18") && saves == 3);
assert(fabs(saved - 5.6639f) < 0.0001f);
// Pack voltages are not limited to single-cell lithium values.
board.multiplier = 1;
board.millivolts = 13000;
assert(run("battery calibrate 13.2") && saves == 4);
assert(fabs(saved - 13200.0f / 13000.0f) < 0.00001f);
const unsigned before = saves;
for (const char* command : {"battery calibrate", "battery calibrate 0",
"battery calibrate 4.2junk", "battery calibrate resetjunk",
"battery calibrate nan", "battery calibrate 4.2", "battery calibrate 65"}) {
assert(run(command) && strncmp(reply, "Error:", 6) == 0 && saves == before);
}
board.millivolts = 0;
assert(run("battery calibrate 13.2") && saves == before);
board.millivolts = UINT16_MAX;
assert(run("battery calibrate 13.2") && saves == before);
// Reset is allowed even while the voltage is unavailable.
assert(run("battery calibrate reset") && saves == before + 1 && saved == 0);
board.millivolts = 4000;
save_ok = false;
const float previous = board.multiplier;
assert(run("battery calibrate 4.18"));
assert(strcmp(reply, "Error: simulated save failure") == 0);
assert(board.multiplier == previous);
board.multiplier = 0;
const unsigned unsupported_saves = saves, unsupported_reads = board.reads;
assert(run("battery calibrate 4.18") && strstr(reply, "unsupported"));
assert(run("battery calibrate reset") && strstr(reply, "unsupported"));
assert(saves == unsupported_saves && board.reads == unsupported_reads);
board.multiplier = NAN;
assert(run("battery calibrate 4.18") && saves == unsupported_saves);
// Replies obey a prefixed Companion console's smaller buffer.
board.multiplier = 1;
char tiny[12];
memset(tiny, 'x', sizeof(tiny));
assert(handleBatteryCalibrationCommand(board, "battery calibrate", tiny, 8, setter));
assert(tiny[7] == 0 && tiny[8] == 'x');
}
'''
BOARD_HARNESS = r'''
#include <MeshCore.h>
#include <cassert>
#include <cmath>
struct ADCBoard : mesh::MainBoard {
uint16_t raw = 4000;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override { return calibrateBatteryMillivolts(raw); }
const char* getManufacturerName() const override { return "test ADC"; }
void reboot() override {}
uint8_t getStartupReason() const override { return 0; }
};
struct UnsupportedBoard : ADCBoard {
bool supportsBatteryCalibration() const override { return false; }
};
int main() {
ADCBoard board;
assert(board.getAdcMultiplier() == 1 && board.getBattMilliVolts() == 4000);
assert(board.setAdcMultiplier(1.05f) && board.getBattMilliVolts() == 4200);
for (float invalid : {NAN, INFINITY, -INFINITY, -1.0f, 0.49f, 1.51f}) {
assert(!board.setAdcMultiplier(invalid));
assert(board.getBattMilliVolts() == 4200);
}
assert(board.setAdcMultiplier(0) && board.getBattMilliVolts() == 4000);
assert(board.setAdcMultiplier(1.5f));
board.raw = 60000;
assert(board.getBattMilliVolts() == UINT16_MAX);
board.raw = 0;
assert(board.getBattMilliVolts() == 0);
UnsupportedBoard unsupported;
assert(unsupported.getAdcMultiplier() == 0);
assert(!unsupported.setAdcMultiplier(1.05f));
}
'''
class BatteryCalibrationTests(unittest.TestCase):
def exercise(self, harness):
with tempfile.TemporaryDirectory(prefix="battery-calibration-") as folder:
source, binary = Path(folder) / "main.cpp", Path(folder) / "check"
source.write_text("#include <initializer_list>\n" + harness)
build = subprocess.run([
os.environ.get("CXX", "g++"), "-std=c++17", "-O1", "-g",
"-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter",
"-fsanitize=address,undefined",
"-fno-sanitize-recover=all", "-fno-pie", "-no-pie", "-I",
str(ROOT / "src"), str(source), "-o", str(binary)
], capture_output=True, text=True, timeout=60)
self.assertEqual(build.returncode, 0, build.stdout + build.stderr)
result = subprocess.run([str(binary)], capture_output=True, text=True, timeout=30)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_production_calibration_commands(self):
self.exercise(HARNESS)
def test_actual_board_calibration(self):
self.exercise(BOARD_HARNESS)
def test_nrf52_boot_policy_takes_priority_without_double_scaling(self):
source = (ROOT / "src/helpers/NRF52Board.cpp").read_text()
methods = "\n".join(extract_braced(source, signature) for signature in (
"uint16_t NRF52Board::calibrateBatteryMillivolts(",
"float NRF52Board::getAdcMultiplier("))
self.exercise(r'''
#define NRF52_POWER_MANAGEMENT 1
#include <MeshCore.h>
#include <helpers/NRF52VoltageRules.h>
#include <cassert>
#include <cmath>
namespace mesh { namespace power {
bool policy_ready = false;
bool voltagePolicySupported() { return policy_ready; }
uint16_t configuredAdcPermille() { return 1050; }
}}
class NRF52Board : public mesh::MainBoard {
public:
bool supportsBatteryCalibration() const override { return true; }
uint16_t calibrateBatteryMillivolts(uint16_t) const;
float getAdcMultiplier() const override;
uint16_t getBattMilliVolts() override { return calibrateBatteryMillivolts(4000); }
const char* getManufacturerName() const override { return "fixture"; }
uint8_t getStartupReason() const override { return 0; }
void reboot() override {}
};
''' + methods + r'''
int main() {
NRF52Board board;
// Early startup has not loaded the boot policy yet.
assert(board.getBattMilliVolts() == 4000 && board.getAdcMultiplier() == 1);
// A board without boot policy uses the common calibration normally.
assert(board.setAdcMultiplier(1.2f) && board.getBattMilliVolts() == 4800);
mesh::power::policy_ready = true;
// A loaded policy is authoritative, rather than stacking two corrections.
assert(board.getBattMilliVolts() == 4200);
assert(fabs(board.getAdcMultiplier() - 1.05f) < 0.00001f);
assert(board.calibrateBatteryMillivolts(0) == 0);
assert(board.calibrateBatteryMillivolts(65000) == UINT16_MAX);
}
''')
if __name__ == "__main__":
unittest.main()
+168
View File
@@ -0,0 +1,168 @@
"""Exercise the production calibration store and rollback across filesystems."""
from pathlib import Path
import os
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
HARNESS = r'''
#include <helpers/BatteryCalibrationStore.h>
#include <helpers/BatteryCalibration.h>
#include <cassert>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
#include <helpers/AtomicFileWriter.h>
#endif
#include "store_under_test.cpp"
struct Board : mesh::MainBoard {
bool capable = true;
uint16_t raw = 4000;
bool supportsBatteryCalibration() const override { return capable; }
uint16_t getBattMilliVolts() override { return calibrateBatteryMillivolts(raw); }
const char* getManufacturerName() const override { return "fixture"; }
uint8_t getStartupReason() const override { return 0; }
void reboot() override {}
void sleep(uint32_t) override {}
};
int main() {
using namespace mesh::power;
MemoryFS fs;
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
fs.rename_replaces = true;
#endif
Board board;
assert(beginBatteryCalibrationStore(board, &fs));
assert(fs.files.empty() && board.getAdcMultiplier() == 1);
char reply[160];
auto run = [&](const char* command) {
strcpy(reply, "untouched");
return handleStoredBatteryCalibrationCommand(board, command, reply, sizeof(reply));
};
assert(run("get adc.multiplier") && !strcmp(reply, "> 1.000"));
assert(!run("get adc.multiplierXYZ") && !strcmp(reply, "untouched"));
assert(run("battery calibrate 4.18") && strstr(reply, "(saved)"));
assert(fabs(board.getAdcMultiplier() - 1.045f) < 0.00001f);
assert(board.getBattMilliVolts() == 4180);
const auto good = fs.files.at("/battery_cal");
assert(good.size() == 12);
Board rebooted;
assert(beginBatteryCalibrationStore(rebooted, &fs));
assert(fabs(rebooted.getAdcMultiplier() - 1.045f) < 0.00001f);
fs.fail_write = true;
assert(run("set adc.multiplier 1.1") && strstr(reply, "save unconfirmed"));
assert(fabs(board.getAdcMultiplier() - 1.045f) < 0.00001f);
assert(fs.files.at("/battery_cal") == good);
fs.fail_write = false;
// Publication can succeed before verification fails. Report uncertainty
// and restore runtime without claiming the old disk image survived.
fs.fail_after_publication = true;
assert(run("set adc.multiplier 1.1") && strstr(reply, "save unconfirmed"));
assert(fabs(board.getAdcMultiplier() - 1.045f) < 0.00001f);
assert(fs.files.at("/battery_cal") != good);
fs.fail_after_publication = false;
fs.fail_read_open = false;
Board published;
assert(beginBatteryCalibrationStore(published, &fs));
assert(fabs(published.getAdcMultiplier() - 1.1f) < 0.00001f);
fs.files["/battery_cal"] = good;
for (const char* command : {"set adc.multiplier nan", "set adc.multiplier 1junk",
"set adc.multiplier -1", "set adc.multiplier 1.6",
"get adc.multiplier junk", "battery calibrate 0"}) {
assert(run(command) && !strncmp(reply, "Error:", 6));
assert(fs.files.at("/battery_cal") == good);
}
// Complete but corrupted records, truncations and invalid factors cannot
// change the factory conversion on a new instance.
for (size_t bit = 0; bit < good.size() * 8; ++bit) {
fs.files["/battery_cal"] = good;
fs.files["/battery_cal"][bit / 8] ^= uint8_t(1U << (bit % 8));
Board fresh;
assert(!beginBatteryCalibrationStore(fresh, &fs));
assert(fresh.getAdcMultiplier() == 1);
}
fs.files["/battery_cal"] = good;
fs.files["/battery_cal"].pop_back();
Board truncated;
assert(!beginBatteryCalibrationStore(truncated, &fs));
assert(truncated.getAdcMultiplier() == 1);
fs.files["/battery_cal"] = good;
assert(run("battery calibrate reset") && strstr(reply, "(saved)"));
assert(board.getAdcMultiplier() == 1);
Board reset;
assert(beginBatteryCalibrationStore(reset, &fs) && reset.getAdcMultiplier() == 1);
board.capable = false;
assert(run("get adc.multiplier") && strstr(reply, "unsupported"));
assert(run("battery calibrate 4.18") && strstr(reply, "unsupported"));
assert(run("set adc.multiplier 1.1") && strstr(reply, "unsupported"));
board.capable = true;
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
// Recover the old record if a reset interrupted the two-rename publication.
fs.files["/battery_cal.bak"] = good;
fs.files.erase("/battery_cal");
Board recovered;
assert(beginBatteryCalibrationStore(recovered, &fs));
assert(recovered.getBattMilliVolts() == 4180);
#endif
char bounded[4] = {'X', 'X', 'X', 'Z'};
assert(handleStoredBatteryCalibrationCommand(board, "get adc.multiplier", bounded, 3));
assert(bounded[2] == 0 && bounded[3] == 'Z');
// A failed mount or volatile fallback must not claim reboot persistence.
assert(!beginBatteryCalibrationStore(board, nullptr));
const float before_missing_fs = board.getAdcMultiplier();
assert(run("battery calibrate 4.18") && strstr(reply, "save unconfirmed"));
assert(board.getAdcMultiplier() == before_missing_fs);
}
'''
class BatteryCalibrationStoreTests(unittest.TestCase):
def test_roundtrip_corruption_write_failure_reset_and_backup_recovery(self):
with tempfile.TemporaryDirectory(prefix="battery-calibration-store-") as temp:
work = Path(temp)
(work / "helpers").mkdir()
mock = ROOT / "test/fixtures/radio_profiles/mocks/helpers/IdentityStore.h"
mock_text = mock.read_text(encoding="ascii")
mock_text = mock_text.replace(" bool fail_read_open = false;",
" bool fail_read_open = false;\n bool fail_after_publication = false;")
mock_text = mock_text.replace(" files[to] = files[from]; files.erase(from); return true;",
" files[to] = files[from]; files.erase(from);\n"
" if (fail_after_publication && !strcmp(to, \"/battery_cal\")) fail_read_open = true;\n"
" return true;")
(work / "helpers/IdentityStore.h").write_text(mock_text, encoding="ascii")
# Redirect only the hardware boundary include. The transaction and
# calibration implementation bodies remain production code.
transaction = (ROOT / "src/helpers/ContactFileTransaction.h").read_text(encoding="ascii")
transaction = transaction.replace('#include "IdentityStore.h"', '#include <helpers/IdentityStore.h>')
transaction = transaction.replace('#include "PersistentStoreFormat.h"', '#include <helpers/PersistentStoreFormat.h>')
(work / "helpers/ContactFileTransaction.h").write_text(transaction, encoding="ascii")
store = (ROOT / "src/helpers/BatteryCalibrationStore.cpp").read_text(encoding="ascii")
for header in ("BatteryCalibrationStore.h", "BatteryCalibration.h", "CheckedSettingsFile.h",
"CLICommandUtils.h", "PersistentStoreFormat.h", "AtomicFileWriter.h",
"ContactFileTransaction.h", "NRF52VoltagePolicy.h"):
store = store.replace(f'#include "{header}"', f'#include <helpers/{header}>')
(work / "store_under_test.cpp").write_text(store, encoding="ascii")
(work / "test.cpp").write_text(HARNESS, encoding="ascii")
for platform in ("ESP32_PLATFORM", "RP2040_PLATFORM", "NRF52_PLATFORM", "STM32_PLATFORM"):
with self.subTest(platform=platform):
binary = work / platform
built = subprocess.run([
os.environ.get("CXX", "g++"), "-std=c++17", "-O1", "-Wall", "-Wextra", "-Werror",
"-Wno-sign-compare", "-Wno-unused-parameter", "-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie", "-D" + platform,
"-I" + str(work), "-I" + str(ROOT / "src"),
str(work / "test.cpp"), "-o", str(binary),
], capture_output=True, text=True, timeout=60)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
result = subprocess.run([str(binary)], capture_output=True, text=True, timeout=20)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
if __name__ == "__main__":
unittest.main()
+83 -3
View File
@@ -101,6 +101,7 @@ struct Board {
float multiplier = 1.0f;
bool adcSupported = true;
uint16_t getBattMilliVolts() { ++reads; return uint16_t(float(mv) * multiplier); }
float getAdcMultiplier() const { return adcSupported ? multiplier : 0.0f; }
bool setAdcMultiplier(float value) {
if (!adcSupported) return false;
multiplier = value == 0.0f ? 1.0f : value;
@@ -288,6 +289,75 @@ void CommonCLI::dispatchVoltage(char* command, char* reply) {
assert(strcmp(reply, "fallthrough") == 0 && board.reads == 5);
''', prefix=prefix, flags=("-DNRF52_PLATFORM", "-DNRF52_POWER_MANAGEMENT"))
def test_actual_adc_get_set_fall_back_when_nrf52_voltage_policy_is_unavailable(self):
source = (ROOT / "src/helpers/CommonCLI.cpp").read_text(encoding="ascii")
setter = extract_braced(source, "void CommonCLI::handleSetCmd(")
getter = extract_braced(source, "void CommonCLI::handleGetCmd(")
def policy_route(method):
start = method.index(" const bool adc_multiplier_command =")
return method[start:method.index("#endif", start)]
set_branch = extract_braced(setter, 'if (memcmp(config, "adc.multiplier ", 15) == 0)')
get_branch = extract_braced(getter, 'if (configKeyEquals(config, "adc.multiplier"))')
prefix = self.runtime_prefix(r'''
struct Prefs { float adc_multiplier = 0.0f; } prefs;
Prefs* _prefs = &prefs;
unsigned prefsSaves = 0;
bool trySavePrefs() { ++prefsSaves; return true; }
bool applyAndSaveAdcMultiplier(float multiplier, char* reply, size_t capacity);
void dispatchSet(char* command, char* reply);
void dispatchGet(char* command, char* reply);
''')
prefix += r'''
#include <math.h>
#include <helpers/CLICommandUtils.h>
namespace mesh { namespace power {
static bool policyAvailable = false;
static unsigned policyCalls = 0;
static bool voltagePolicySupported() { return policyAvailable; }
static bool handleVoltagePolicyCommand(const char*, char* reply, size_t capacity) {
++policyCalls;
snprintf(reply, capacity, "%s", policyAvailable ? "> policy" : "Error: voltage policy unsupported");
return true;
}
}}
'''
prefix += extract_braced(source, "static bool configKeyEquals(") + "\n"
prefix += extract_braced(source, "bool CommonCLI::applyAndSaveAdcMultiplier(") + "\n"
prefix += "void CommonCLI::dispatchSet(char* command, char* reply) {\n"
prefix += "const char* config = command + 4;\n" + policy_route(setter) + set_branch + "\n}\n"
prefix += "void CommonCLI::dispatchGet(char* command, char* reply) {\n"
prefix += "const char* config = command + 4;\n" + policy_route(getter) + get_branch + "\n}\n"
self.exercise(r'''
Board board;
CommonCLI cli(board);
char reply[160];
char get[] = "get adc.multiplier";
cli.dispatchGet(get, reply);
assert(!strcmp(reply, "> 1.000") && mesh::power::policyCalls == 0);
char set[] = "set adc.multiplier 1.2";
cli.dispatchSet(set, reply);
assert(!strncmp(reply, "OK", 2) && cli.prefsSaves == 1);
assert(board.multiplier == 1.2f && cli.prefs.adc_multiplier == 1.2f);
assert(mesh::power::policyCalls == 0);
cli.dispatchGet(get, reply);
assert(!strcmp(reply, "> 1.200") && mesh::power::policyCalls == 0);
char profile[] = "set battery.profile lifepo4";
cli.dispatchSet(profile, reply);
assert(strstr(reply, "voltage policy unsupported") && mesh::power::policyCalls == 1);
char full[] = "get battery.full";
cli.dispatchGet(full, reply);
assert(strstr(reply, "voltage policy unsupported") && mesh::power::policyCalls == 2);
board.adcSupported = false;
cli.dispatchGet(get, reply);
assert(!strcmp(reply, "Error: unsupported") && mesh::power::policyCalls == 2);
cli.dispatchSet(set, reply);
assert(!strcmp(reply, "Error: unsupported") && mesh::power::policyCalls == 2);
board.adcSupported = true;
mesh::power::policyAvailable = true;
cli.dispatchGet(get, reply);
assert(!strcmp(reply, "> policy") && mesh::power::policyCalls == 3);
''', prefix=prefix, flags=("-DNRF52_PLATFORM", "-DNRF52_POWER_MANAGEMENT"))
def test_actual_set_command_adc_branch_resamples_success_and_skips_rejected_changes(self):
source = (ROOT / "src/helpers/CommonCLI.cpp").read_text(encoding="ascii")
setter = extract_braced(source, "void CommonCLI::handleSetCmd(")
@@ -298,10 +368,13 @@ void CommonCLI::dispatchVoltage(char* command, char* reply) {
struct Prefs { float adc_multiplier = 0.0f; } prefs;
Prefs* _prefs = &prefs;
unsigned prefsSaves = 0;
void savePrefs() { ++prefsSaves; }
bool prefsSaveOk = true;
bool trySavePrefs() { ++prefsSaves; return prefsSaveOk; }
bool applyAndSaveAdcMultiplier(float multiplier, char* reply, size_t capacity);
void dispatchAdc(char* command, char* reply);
''')
prefix += "\nnamespace mesh { namespace cli {\n" + parser + "\n}}\n"
prefix += "\n#include <math.h>\nnamespace mesh { namespace cli {\n" + parser + "\n}}\n"
prefix += extract_braced(source, "bool CommonCLI::applyAndSaveAdcMultiplier(") + "\n"
prefix += "void CommonCLI::dispatchAdc(char* command, char* reply) {\n"
prefix += "const char* config = &command[4];\n" + branch + "\n}\n"
self.exercise(r'''
@@ -329,10 +402,17 @@ void CommonCLI::dispatchVoltage(char* command, char* reply) {
assert(strcmp(reply, "Error: unsupported") == 0 && cli.prefsSaves == 1 && board.reads == 2);
assert(cli.prefs.adc_multiplier == 1.2f && cli.batteryAllowsForward(false));
board.adcSupported = true;
cli.prefsSaveOk = false;
char failed[] = "set adc.multiplier 1.0";
cli.dispatchAdc(failed, reply);
assert(strstr(reply, "save unconfirmed") && cli.prefsSaves == 2 && board.reads == 2);
assert(cli.prefs.adc_multiplier == 1.2f && board.multiplier == 1.2f);
assert(cli.batteryAllowsForward(false));
cli.prefsSaveOk = true;
char reset[] = "set adc.multiplier 0";
cli.dispatchAdc(reset, reply);
assert(strcmp(reply, "OK - using default board multiplier") == 0);
assert(cli.prefsSaves == 2 && board.reads == 3 && cli._battery_forward_state.percentage == 41);
assert(cli.prefsSaves == 3 && board.reads == 3 && cli._battery_forward_state.percentage == 41);
assert(!cli.batteryAllowsForward(false));
''', prefix=prefix, flags=("-DESP32_PLATFORM",))
+2
View File
@@ -30,6 +30,7 @@ inline uint32_t analogReadMilliVolts(int) { return 830; }
BASE_BOARD = r'''
#pragma once
#include <Arduino.h>
#include <math.h>
#include <helpers/LedPolicy.h>
class ESP32Board {
protected:
@@ -48,6 +49,7 @@ public:
virtual uint16_t getBattMilliVolts() { return 0; }
virtual bool isExternalPowered() { return false; }
virtual bool isUsbHostConnected() { return false; }
virtual bool supportsBatteryCalibration() const { return false; }
virtual bool setAdcMultiplier(float) { return false; }
virtual float getAdcMultiplier() const { return 0; }
virtual const char* getManufacturerName() const { return "test"; }
+9 -2
View File
@@ -139,6 +139,7 @@ class RAK3x72BatteryScaleTest(unittest.TestCase):
#include <cassert>
#include <cstdint>
#include <limits>
#include <helpers/BatteryCalibration.h>
#define PIN_VBAT_READ 7
#define ADC_MULTIPLIER @SCALE@
static unsigned reads, resolutions;
@@ -151,7 +152,12 @@ int analogRead(int pin) {
remaining -= value;
return value;
}
class Board { public: @BODY@ };
class Board {
uint16_t calibrateBatteryMillivolts(uint16_t raw) const {
return mesh::power::scaleBatteryMillivolts(raw, 1.0f);
}
public: @BODY@
};
static_assert(ADC_MULTIPLIER == 8650, "revisit the fixed-point scale if calibration changes");
static_assert(uint64_t(8) * 4095 * 8650 <= std::numeric_limits<uint32_t>::max(),
"battery accumulation product must not overflow");
@@ -172,7 +178,8 @@ int main() {
cpp.write_text(source)
binary = root / "battery"
subprocess.run(["g++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
*SANITIZER_FLAGS, str(cpp), "-o", str(binary)], check=True)
*SANITIZER_FLAGS, "-I", str(ROOT / "src"),
str(cpp), "-o", str(binary)], check=True)
subprocess.run([str(binary)], check=True)
@@ -17,6 +17,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -17,6 +17,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -17,6 +17,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -17,6 +17,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
+9 -1
View File
@@ -36,6 +36,14 @@ public:
#endif
}
bool supportsBatteryCalibration() const override {
#ifdef PIN_VBAT_READ
return true;
#else
return false;
#endif
}
uint16_t getBattMilliVolts() override {
#ifdef PIN_VBAT_READ
analogReadResolution(12); // ESP32-C3 ADC is 12-bit - 3.3/4096 (ref voltage/max counts)
@@ -45,7 +53,7 @@ public:
}
raw = raw / 8;
return ((6.52 * raw) / 1024.0) * 1000;
return calibrateBatteryMillivolts(((6.52 * raw) / 1024.0) * 1000);
#else
return 0; // not supported
#endif
+1 -1
View File
@@ -33,7 +33,7 @@ void HeltecE213Board::begin() {
digitalWrite(PIN_ADC_CTRL, LOW);
return (5.42 * (3.3 / 1024.0) * raw) * 1000;
return calibrateBatteryMillivolts((5.42 * (3.3 / 1024.0) * raw) * 1000);
}
const char* HeltecE213Board::getManufacturerName() const {
+2
View File
@@ -12,6 +12,8 @@ public:
HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
};
+1 -1
View File
@@ -33,7 +33,7 @@ void HeltecE290Board::begin() {
digitalWrite(PIN_ADC_CTRL, LOW);
return (5.42 * (3.3 / 1024.0) * raw) * 1000;
return calibrateBatteryMillivolts((5.42 * (3.3 / 1024.0) * raw) * 1000);
}
const char* HeltecE290Board::getManufacturerName() const {
+2
View File
@@ -12,6 +12,8 @@ public:
HeltecE290Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { }
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
+1
View File
@@ -79,6 +79,7 @@ uint16_t HeltecRC32Board::getBattMilliVolts() {
}
bool HeltecRC32Board::setAdcMultiplier(float multiplier) {
if (!isfinite(multiplier) || multiplier < 0.0f || multiplier > 10.0f) return false;
adc_mult = multiplier == 0.0f ? ADC_MULTIPLIER : multiplier;
return true;
}
+2
View File
@@ -23,6 +23,8 @@ public:
void powerOff() override;
void onBeforeTransmit() override;
void onAfterTransmit() override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
bool setAdcMultiplier(float multiplier) override;
float getAdcMultiplier() const override { return adc_mult; }
+2
View File
@@ -28,6 +28,8 @@ public:
void applyLedPolicy() override;
void onBeforeTransmit(void) override;
void onAfterTransmit(void) override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
void shutdownPeripherals() override;
+2
View File
@@ -21,6 +21,8 @@ public:
void applyLedPolicy() override;
void onBeforeTransmit() override;
void onAfterTransmit() override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override;
void shutdownPeripherals() override;
+2
View File
@@ -33,6 +33,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
int adcvalue = 0;
analogReadResolution(12);
+1 -1
View File
@@ -33,7 +33,7 @@ void HeltecT190Board::begin() {
digitalWrite(PIN_ADC_CTRL, LOW);
return (5.42 * (3.3 / 1024.0) * raw) * 1000;
return calibrateBatteryMillivolts((5.42 * (3.3 / 1024.0) * raw) * 1000);
}
const char* HeltecT190Board::getManufacturerName() const {
+2
View File
@@ -12,6 +12,8 @@ public:
HeltecT190Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
@@ -19,6 +19,8 @@ public:
void applyLedPolicy() override;
void onBeforeTransmit() override;
void onAfterTransmit() override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override;
void powerOff() override;
@@ -59,7 +59,7 @@ void HeltecTrackerV2Board::begin() {
digitalWrite(PIN_ADC_CTRL, LOW);
return (5.42 * (3.3 / 1024.0) * raw) * 1000;
return calibrateBatteryMillivolts((5.42 * (3.3 / 1024.0) * raw) * 1000);
}
const char* HeltecTrackerV2Board::getManufacturerName() const {
@@ -21,6 +21,8 @@ public:
void onBeforeTransmit(void) override;
void onAfterTransmit(void) override;
void shutdownPeripherals() override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
bool setLoRaFemLnaEnabled(bool enable) override;
+3 -1
View File
@@ -25,6 +25,8 @@ public:
}
}
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(10);
@@ -34,7 +36,7 @@ public:
}
raw = raw / 8;
return (1.98 * (2 / 1024.0) * raw) * 1000;
return calibrateBatteryMillivolts((1.98 * (2 / 1024.0) * raw) * 1000);
}
const char* getManufacturerName() const override {
+3 -1
View File
@@ -51,6 +51,8 @@ public:
}
}
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(10);
digitalWrite(PIN_ADC_CTRL, adc_active_state);
@@ -63,7 +65,7 @@ public:
digitalWrite(PIN_ADC_CTRL, !adc_active_state);
return (ADC_MULTIPLIER * (3.3 / 1024.0) * raw) * 1000;
return calibrateBatteryMillivolts((ADC_MULTIPLIER * (3.3 / 1024.0) * raw) * 1000);
}
const char* getManufacturerName() const override {
+3
View File
@@ -30,8 +30,11 @@ public:
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
bool setAdcMultiplier(float multiplier) override {
if (!isfinite(multiplier) || multiplier < 0.0f || multiplier > 10.0f) return false;
if (multiplier == 0.0f) {
adc_mult = ADC_MULTIPLIER;
} else {
+3
View File
@@ -32,9 +32,12 @@ public:
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
bool isExternalPowered() override;
bool setAdcMultiplier(float multiplier) override {
if (!isfinite(multiplier) || multiplier < 0.0f || multiplier > 10.0f) return false;
if (multiplier == 0.0f) {
adc_mult = ADC_MULTIPLIER;
} else {
@@ -25,6 +25,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
// Please read before going further ;)
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
@@ -38,7 +40,7 @@ public:
analogReference(AR_INTERNAL_3_0);
delay(10);
adcvalue = analogRead(PIN_VBAT);
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
return calibrateBatteryMillivolts((adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096);
}
const char* getManufacturerName() const override {
+3 -1
View File
@@ -33,6 +33,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
// Please read before going further ;)
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
@@ -46,7 +48,7 @@ public:
analogReference(AR_INTERNAL_3_0);
delay(10);
adcvalue = analogRead(PIN_VBAT);
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
return calibrateBatteryMillivolts((adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096);
}
const char *getManufacturerName() const override {
@@ -33,6 +33,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
// Please read before going further ;)
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
@@ -46,7 +48,7 @@ public:
analogReference(AR_INTERNAL_3_0);
delay(10);
adcvalue = analogRead(PIN_VBAT);
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
return calibrateBatteryMillivolts((adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096);
}
const char *getManufacturerName() const override {
+3 -1
View File
@@ -14,6 +14,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -22,7 +24,7 @@ public:
raw += analogRead(PIN_VBAT_READ);
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw);
return calibrateBatteryMillivolts((ADC_MULTIPLIER * raw));
}
const char* getManufacturerName() const override {
@@ -26,6 +26,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
// enable battery voltage measurement
digitalWrite(BATTERY_MEASUREMENT_CONTROL, HIGH);
@@ -45,7 +47,7 @@ public:
// disable battery voltage measurement
digitalWrite(BATTERY_MEASUREMENT_CONTROL, LOW);
return ((adcvalue * ((3000.0 / 4096.0)))) * 2.0;
return calibrateBatteryMillivolts(((adcvalue * ((3000.0 / 4096.0)))) * 2.0);
}
const char* getManufacturerName() const override {
+2 -2
View File
@@ -126,8 +126,8 @@ uint16_t TBeam1WBoard::getBattMilliVolts() {
delayMicroseconds(100);
}
raw /= BATTERY_SENSE_SAMPLES;
return static_cast<uint16_t>(
(static_cast<float>(raw) / 4095.0f) * 3300.0f * ADC_MULTIPLIER);
return calibrateBatteryMillivolts(static_cast<uint16_t>(
(static_cast<float>(raw) / 4095.0f) * 3300.0f * ADC_MULTIPLIER));
}
const char* TBeam1WBoard::getManufacturerName() const {
+2
View File
@@ -74,6 +74,8 @@ public:
void applyLedPolicy() override;
void onBeforeTransmit() override;
void onAfterTransmit() override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override;
void powerOff() override;
+3 -1
View File
@@ -26,6 +26,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() {
#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER)
analogReadResolution(12);
@@ -36,7 +38,7 @@ public:
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw) / 4096;
return calibrateBatteryMillivolts((ADC_MULTIPLIER * raw) / 4096);
#else
return 0;
#endif
+1 -1
View File
@@ -26,6 +26,6 @@ uint16_t TechoBoard::getBattMilliVolts() {
adcvalue = analogRead(PIN_VBAT_READ);
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB));
}
#endif
+2
View File
@@ -17,6 +17,8 @@ class TechoBoard : public NRF52BoardDCDC {
public:
TechoBoard() : NRF52Board("TECHO_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override {
@@ -54,7 +54,7 @@ uint16_t TechoCardBoard::getBattMilliVolts() {
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB));
}
void TechoCardBoard::applyLedPolicy() {
@@ -19,6 +19,8 @@ class TechoCardBoard : public NRF52BoardDCDC {
public:
TechoCardBoard() : NRF52Board("TECHO_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
void applyLedPolicy() override;
void onBeforeTransmit(void) override;
+1 -1
View File
@@ -49,6 +49,6 @@ uint16_t TechoBoard::getBattMilliVolts() {
// = adc * 0.73242188 * 2.0 = adc * 1.46484375
uint16_t millivolts = (uint16_t)((float)adc * (3000.0f / 4096.0f) * 2.0f);
return millivolts;
return calibrateBatteryMillivolts(millivolts);
}
#endif
+2
View File
@@ -15,6 +15,8 @@ class TechoBoard : public NRF52BoardDCDC {
public:
TechoBoard() : NRF52Board("TECHO_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override {
@@ -10,6 +10,8 @@ public:
return "LILYGO T-LoRa V2.1-1.6";
}
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -19,7 +21,7 @@ public:
}
raw = raw / 8;
return (2 * raw);
return calibrateBatteryMillivolts((2 * raw));
}
uint32_t getIRQGpio() override {
+3 -1
View File
@@ -14,6 +14,8 @@ public:
HeltecMeshPocket() : NRF52Board("MESH_POCKET_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
int adcvalue = 0;
analogReadResolution(12);
@@ -26,7 +28,7 @@ public:
adcvalue = analogRead(PIN_VBAT_READ);
digitalWrite(PIN_BAT_CTL, LOW);
return (uint16_t)((float)adcvalue * MV_LSB * 4.9);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * MV_LSB * 4.9));
}
const char* getManufacturerName() const override {
@@ -34,6 +34,8 @@ public:
}
}
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -43,7 +45,7 @@ public:
}
raw = raw / 4;
return (2 * raw);
return calibrateBatteryMillivolts((2 * raw));
}
const char* getManufacturerName() const override {
@@ -28,8 +28,11 @@ public:
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
bool prepareRadioFrequency(float frequency) override;
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
bool setAdcMultiplier(float multiplier) override {
if (!isfinite(multiplier) || multiplier < 0.0f || multiplier > 10.0f) return false;
if (multiplier == 0.0f) {
adc_mult = ADC_MULTIPLIER;
} else {
+3 -1
View File
@@ -26,13 +26,15 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
int adcvalue = 0;
analogReadResolution(12);
analogReference(AR_INTERNAL_3_0);
delay(10);
adcvalue = analogRead(PIN_VBAT_READ);
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
return calibrateBatteryMillivolts((adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096);
}
const char *getManufacturerName() const override { return "Meshtiny"; }
+9 -1
View File
@@ -12,6 +12,14 @@ public:
MeshTrackerX1Board() : NRF52Board("X1_OTA") {}
void begin();
bool supportsBatteryCalibration() const override {
#ifdef BATTERY_PIN
return true;
#else
return false;
#endif
}
uint16_t getBattMilliVolts() override {
#ifdef BATTERY_PIN
#ifdef PIN_BAT_ADC_EN
@@ -32,7 +40,7 @@ public:
if (raw < 0 || raw > 4095) return 0;
float volts = (raw * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096;
return volts * 1000;
return calibrateBatteryMillivolts(volts * 1000);
#else
return 0;
#endif
@@ -30,6 +30,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -38,7 +40,7 @@ public:
raw += analogRead(PIN_VBAT_READ);
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw);
return calibrateBatteryMillivolts(ADC_MULTIPLIER * raw);
}
const char* getManufacturerName() const override {
+2
View File
@@ -27,6 +27,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
analogReference(AR_INTERNAL_3_0);
+2
View File
@@ -49,6 +49,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
MESH_DEBUG_PRINTLN("R1Neo: Sampling battery");
analogReadResolution(12);
+1 -1
View File
@@ -37,6 +37,6 @@ uint16_t NanoG2Ultra::getBattMilliVolts()
adcvalue = analogRead(PIN_VBAT_READ);
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB));
}
#endif
+2
View File
@@ -39,6 +39,8 @@ class NanoG2Ultra : public NRF52BoardDCDC {
public:
NanoG2Ultra() : NRF52Board("NANO_G2_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char *getManufacturerName() const override { return "Nano G2 Ultra"; }
+3
View File
@@ -18,6 +18,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -30,6 +32,7 @@ public:
}
bool setAdcMultiplier(float multiplier) override {
if (!isfinite(multiplier) || multiplier < 0.0f || multiplier > 10.0f) return false;
if (multiplier == 0.0f) {
adc_mult = ADC_MULTIPLIER;}
else {
+3 -1
View File
@@ -32,6 +32,8 @@ public:
void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); }
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER)
analogReadResolution(12);
@@ -42,7 +44,7 @@ public:
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw);
return calibrateBatteryMillivolts((ADC_MULTIPLIER * raw));
#else
return 0;
#endif
+3 -1
View File
@@ -50,6 +50,8 @@ public:
}
}
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -59,7 +61,7 @@ public:
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw) / 4096;
return calibrateBatteryMillivolts((ADC_MULTIPLIER * raw) / 4096);
}
const char* getManufacturerName() const override {
+2
View File
@@ -31,6 +31,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
+3 -1
View File
@@ -23,6 +23,8 @@ public:
return "RAK 3x72";
}
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
uint32_t raw = 0;
@@ -32,7 +34,7 @@ public:
// The eight 12-bit samples sum to at most 32760. This is the exact
// previous 8650 / 32768 scale and truncation without double helpers;
// the largest product (283374000) fits in uint32_t.
return (uint16_t)((raw * 8650UL) >> 15);
return calibrateBatteryMillivolts((uint16_t)((raw * 8650UL) >> 15));
}
void setGpio(uint32_t values) override {
+2
View File
@@ -21,6 +21,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -29,6 +29,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -38,7 +40,7 @@ public:
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw) / 4096;
return calibrateBatteryMillivolts((ADC_MULTIPLIER * raw) / 4096);
}
const char* getManufacturerName() const override {
+3 -1
View File
@@ -33,6 +33,8 @@ public:
#define BATTERY_SAMPLES 8
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
@@ -42,7 +44,7 @@ public:
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw) / 4096;
return calibrateBatteryMillivolts((ADC_MULTIPLIER * raw) / 4096);
}
const char* getManufacturerName() const override {
@@ -28,6 +28,8 @@ public:
}
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
digitalWrite(VBAT_ENABLE, LOW);
int adcvalue = 0;
+2
View File
@@ -21,6 +21,8 @@ public:
}
}
bool supportsBatteryCalibration() const override { return false; }
uint16_t getBattMilliVolts() override {
return 0;
}
@@ -76,6 +76,8 @@ public:
void shutdownPeripherals() override;
bool supportsBatteryCalibration() const override { return false; }
uint16_t getBattMilliVolts() override {
return 0;
}
+9 -1
View File
@@ -12,6 +12,14 @@ public:
T1000eBoard() : NRF52Board("T1000E_OTA") {}
void begin();
bool supportsBatteryCalibration() const override {
#ifdef BATTERY_PIN
return true;
#else
return false;
#endif
}
uint16_t getBattMilliVolts() override {
#ifdef BATTERY_PIN
#ifdef PIN_3V3_EN
@@ -28,7 +36,7 @@ public:
analogReference(AR_DEFAULT); // put back to default
analogReadResolution(10);
return volts * 1000;
return calibrateBatteryMillivolts(volts * 1000);
#else
return 0;
#endif
+1 -1
View File
@@ -31,6 +31,6 @@ uint16_t ThinkNodeM1Board::getBattMilliVolts() {
adcvalue = analogRead(PIN_VBAT_READ);
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB));
}
#endif
+2
View File
@@ -17,6 +17,8 @@ class ThinkNodeM1Board : public NRF52Board {
public:
ThinkNodeM1Board() : NRF52Board("THINKNODE_M1_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
#if defined(P_LORA_TX_LED)
+1 -1
View File
@@ -22,7 +22,7 @@ uint16_t ThinknodeM2Board::getBattMilliVolts() {
mv /= 8;
analogReadResolution(10);
return static_cast<uint16_t>(mv * ADC_MULTIPLIER);
return calibrateBatteryMillivolts(static_cast<uint16_t>(mv * ADC_MULTIPLIER));
}
const char *ThinknodeM2Board::getManufacturerName() const {
+2
View File
@@ -8,6 +8,8 @@ class ThinknodeM2Board : public ESP32Board {
public:
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
+1 -1
View File
@@ -24,5 +24,5 @@ uint16_t ThinkNodeM3Board::getBattMilliVolts() {
adcvalue = analogRead(PIN_VBAT_READ);
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * ADC_FACTOR);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * ADC_FACTOR));
}
+2
View File
@@ -16,6 +16,8 @@ protected:
public:
ThinkNodeM3Board() : NRF52Board("THINKNODE_M3_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
#ifdef P_LORA_TX_LED
+1 -1
View File
@@ -31,7 +31,7 @@ void ThinknodeM5Board::begin() {
mv /= 8;
analogReadResolution(10);
return static_cast<uint16_t>(mv * ADC_MULTIPLIER );
return calibrateBatteryMillivolts(static_cast<uint16_t>(mv * ADC_MULTIPLIER ));
}
const char* ThinknodeM5Board::getManufacturerName() const {
+2
View File
@@ -12,6 +12,8 @@ class ThinknodeM5Board : public ESP32Board {
public:
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
+1 -1
View File
@@ -34,6 +34,6 @@ uint16_t ThinkNodeM6Board::getBattMilliVolts() {
digitalWrite(PIN_ADC_CTRL, LOW);
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB));
}
#endif
+2
View File
@@ -21,6 +21,8 @@ protected:
public:
ThinkNodeM6Board() : NRF52Board("THINKNODE_M6_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
#if defined(P_LORA_TX_LED)
+1 -1
View File
@@ -33,6 +33,6 @@ uint16_t ThinkNodeM8Board::getBattMilliVolts() {
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
digitalWrite(ADC_EN, LOW);
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
return calibrateBatteryMillivolts((uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB));
}
#endif
+2
View File
@@ -17,6 +17,8 @@ class ThinkNodeM8Board : public NRF52Board {
public:
ThinkNodeM8Board() : NRF52Board("THINKNODE_M8_OTA") {}
void begin();
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override;
#if defined(P_LORA_TX_LED)
+3 -1
View File
@@ -23,6 +23,8 @@ class TinyRelayBoard : public STM32Board
const char *getManufacturerName() const override { return "Tiny Relay"; }
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override
{
analogReadResolution(12);
@@ -30,7 +32,7 @@ class TinyRelayBoard : public STM32Board
for (int i = 0; i < 8; i++) {
raw += analogRead(PIN_VBAT_READ);
}
return ((double)raw) * ADC_MULTIPLIER / 8 / 4096;
return calibrateBatteryMillivolts(((double)raw) * ADC_MULTIPLIER / 8 / 4096);
}
void setGpio(uint32_t values) override
@@ -44,6 +44,8 @@ public:
void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); }
#endif
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER)
analogReadResolution(12);
@@ -54,7 +56,7 @@ public:
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw) / 4096;
return calibrateBatteryMillivolts((ADC_MULTIPLIER * raw) / 4096);
#else
return 0;
#endif
+1
View File
@@ -51,6 +51,7 @@ build_flags = ${lora_e5_mini.build_flags}
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-fno-schedule-insns2
-fno-guess-branch-probability ; measured flash saving; retain calibration and routing
-Wl,--sort-section=alignment
-D RADIO_LIVENESS_SOFT_ONLY=1 ; integrated STM32WL has no separate radio-reset pin
-D WIO_E5_MINI_NO_EXTERNAL_SENSORS=1 ; the dedicated sensor env retains BME280 support
+3 -1
View File
@@ -34,6 +34,8 @@ public:
return "Seeed Wio E5 mini";
}
bool supportsBatteryCalibration() const override { return true; }
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
uint32_t raw = 0;
@@ -43,7 +45,7 @@ public:
// 1.73 * 5 V over eight 12-bit samples is raw * 8650 / 32768.
// Fixed-point math avoids pulling double-precision helpers into this
// flash-constrained STM32WL build; the half-divisor rounds to nearest.
return (uint16_t)((raw * 8650UL + 16384UL) >> 15);
return calibrateBatteryMillivolts((uint16_t)((raw * 8650UL + 16384UL) >> 15));
}
};
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff. Show more