mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 11:02:21 +00:00
Add persistent battery ADC calibration across boards
Add meter-reference calibration with saved ADC corrections, checked persistence and rollback. Preserve existing board coefficients and nRF52 voltage policy, and merge the Mini Repeater feature gates. Validation: 69 focused tests, hosted documentation checks, and four firmware builds passed. Mini Repeater retains calibration and its existing filesystem with 376 flash bytes free; earlier Mini Sensor size failures remain documented.
This commit is contained in:
1 parent
94a3b7e568
commit
ab6412e73c
108 files changed
+1303
-83
No files matched your search
@@ -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
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2692,7 +2692,6 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
board.setAdcMultiplier(_prefs.adc_multiplier);
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1316,7 +1316,6 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
board.setAdcMultiplier(_prefs.adc_multiplier);
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
|
||||
+18
-2
@@ -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() { }
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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",))
|
||||
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ;
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,6 +33,8 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
bool supportsBatteryCalibration() const override { return true; }
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
int adcvalue = 0;
|
||||
analogReadResolution(12);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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"; }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
|
||||
#define BATTERY_SAMPLES 8
|
||||
|
||||
bool supportsBatteryCalibration() const override { return true; }
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
analogReadResolution(12);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in new issue
Block a user