Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc65b19cf2 | ||
|
|
e1bca5a81a | ||
|
|
845e4f9509 |
+30
-7
@@ -1,3 +1,5 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "esp_adc/adc_cali_scheme.h"
|
#include "esp_adc/adc_cali_scheme.h"
|
||||||
#include "esp_adc/adc_oneshot.h"
|
#include "esp_adc/adc_oneshot.h"
|
||||||
@@ -11,7 +13,13 @@ static const char *TAG = "battery";
|
|||||||
#if CONFIG_FRAME_BATTERY_ADC_GPIO >= 0
|
#if CONFIG_FRAME_BATTERY_ADC_GPIO >= 0
|
||||||
|
|
||||||
#define BATTERY_ADC_GPIO CONFIG_FRAME_BATTERY_ADC_GPIO
|
#define BATTERY_ADC_GPIO CONFIG_FRAME_BATTERY_ADC_GPIO
|
||||||
#define BATTERY_SAMPLES 8
|
#define BATTERY_SAMPLES 16
|
||||||
|
/* Trimmed mean: the extreme BATTERY_TRIM samples on each end (regulator/
|
||||||
|
* RF transients, not the true resting voltage) are dropped before
|
||||||
|
* averaging the rest -- a plain average lets even one or two of those
|
||||||
|
* skew the result enough to read as a real percent change downstream
|
||||||
|
* (see the recharge-jump handling in routers/device.py). */
|
||||||
|
#define BATTERY_TRIM 3
|
||||||
/* The external divider halves the battery voltage (2x200k, per the
|
/* The external divider halves the battery voltage (2x200k, per the
|
||||||
* Seeed-documented XIAO wiring) so a full 4.2V cell reads ~2.1V at the
|
* Seeed-documented XIAO wiring) so a full 4.2V cell reads ~2.1V at the
|
||||||
* pin, inside the 12dB-attenuation ADC range. */
|
* pin, inside the 12dB-attenuation ADC range. */
|
||||||
@@ -34,6 +42,11 @@ static const struct {
|
|||||||
{ 3300, 5 }, { 3000, 0 },
|
{ 3300, 5 }, { 3000, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int int_cmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return *(const int *)a - *(const int *)b;
|
||||||
|
}
|
||||||
|
|
||||||
static int mv_to_percent(int mv)
|
static int mv_to_percent(int mv)
|
||||||
{
|
{
|
||||||
int n = sizeof(LIPO_CURVE) / sizeof(LIPO_CURVE[0]);
|
int n = sizeof(LIPO_CURVE) / sizeof(LIPO_CURVE[0]);
|
||||||
@@ -139,19 +152,17 @@ int battery_read_percent(void)
|
|||||||
ESP_LOGW(TAG, "ADC calibration unavailable, using nominal scaling");
|
ESP_LOGW(TAG, "ADC calibration unavailable, using nominal scaling");
|
||||||
}
|
}
|
||||||
|
|
||||||
int mv_sum = 0;
|
int mv_samples[BATTERY_SAMPLES];
|
||||||
int samples = 0;
|
int samples = 0;
|
||||||
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
||||||
int value;
|
int value;
|
||||||
if (calibrated) {
|
if (calibrated) {
|
||||||
if (adc_oneshot_get_calibrated_result(adc, cali, channel, &value) == ESP_OK) {
|
if (adc_oneshot_get_calibrated_result(adc, cali, channel, &value) == ESP_OK) {
|
||||||
mv_sum += value;
|
mv_samples[samples++] = value;
|
||||||
samples++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (adc_oneshot_read(adc, channel, &value) == ESP_OK) {
|
if (adc_oneshot_read(adc, channel, &value) == ESP_OK) {
|
||||||
mv_sum += value * 3300 / 4095; /* nominal 12-bit full scale at 12dB */
|
mv_samples[samples++] = value * 3300 / 4095; /* nominal 12-bit full scale at 12dB */
|
||||||
samples++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +178,19 @@ int battery_read_percent(void)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int battery_mv = (mv_sum / samples) * BATTERY_DIVIDER_RATIO;
|
/* Only trim if there's enough left afterward to still be a
|
||||||
|
* meaningful average -- falls back to a plain average of whatever
|
||||||
|
* came in on a wake where most reads failed. */
|
||||||
|
qsort(mv_samples, samples, sizeof(int), int_cmp);
|
||||||
|
int trim = (samples > 2 * BATTERY_TRIM) ? BATTERY_TRIM : 0;
|
||||||
|
int mv_sum = 0;
|
||||||
|
int kept = 0;
|
||||||
|
for (int i = trim; i < samples - trim; i++) {
|
||||||
|
mv_sum += mv_samples[i];
|
||||||
|
kept++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int battery_mv = (mv_sum / kept) * BATTERY_DIVIDER_RATIO;
|
||||||
if (battery_mv < BATTERY_MV_MIN || battery_mv > BATTERY_MV_MAX) {
|
if (battery_mv < BATTERY_MV_MIN || battery_mv > BATTERY_MV_MAX) {
|
||||||
ESP_LOGI(TAG, "Reading %dmV outside plausible battery range, ignoring", battery_mv);
|
ESP_LOGI(TAG, "Reading %dmV outside plausible battery range, ignoring", battery_mv);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.2.3
|
1.2.4
|
||||||
|
|||||||
@@ -21,7 +21,16 @@ logger = logging.getLogger(__name__)
|
|||||||
# Battery-history / estimate tuning (see /frame/battery and battery_estimate_s).
|
# Battery-history / estimate tuning (see /frame/battery and battery_estimate_s).
|
||||||
BATTERY_HISTORY_MAX = 500 # ~20 days at hourly reports
|
BATTERY_HISTORY_MAX = 500 # ~20 days at hourly reports
|
||||||
BATTERY_LOG_MAX = 20000 # ~2 years at hourly reports -- cap on the battery_log table per frame
|
BATTERY_LOG_MAX = 20000 # ~2 years at hourly reports -- cap on the battery_log table per frame
|
||||||
RECHARGE_JUMP_PCT = 5 # a report this much above the previous one = battery was recharged
|
RECHARGE_JUMP_PCT = 5 # a report this much above the recent baseline = battery was recharged
|
||||||
|
# How many of the most recent reports make up that baseline. A lone noisy
|
||||||
|
# reading (ADC/regulator glitch -- see firmware/main/battery.c) can still
|
||||||
|
# dip or spike a single report; comparing against just the one immediately
|
||||||
|
# previous report meant that a normal reading right after a noisy dip
|
||||||
|
# looked like a 5%+ jump and falsely registered as a recharge. Comparing
|
||||||
|
# against the max of the last few reports instead means an actual recharge
|
||||||
|
# still needs to clear all of them, while a single stray low one doesn't
|
||||||
|
# get to set the bar.
|
||||||
|
RECHARGE_LOOKBACK = 3
|
||||||
MIN_ESTIMATE_SPAN_S = 2 * 3600 # need at least this much observed time...
|
MIN_ESTIMATE_SPAN_S = 2 * 3600 # need at least this much observed time...
|
||||||
MIN_ESTIMATE_DROP_PCT = 2 # ...and this much observed drop before estimating
|
MIN_ESTIMATE_DROP_PCT = 2 # ...and this much observed drop before estimating
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from .common import (
|
|||||||
BATTERY_HISTORY_MAX,
|
BATTERY_HISTORY_MAX,
|
||||||
BATTERY_LOG_MAX,
|
BATTERY_LOG_MAX,
|
||||||
RECHARGE_JUMP_PCT,
|
RECHARGE_JUMP_PCT,
|
||||||
|
RECHARGE_LOOKBACK,
|
||||||
immich_client_for,
|
immich_client_for,
|
||||||
immich_creds,
|
immich_creds,
|
||||||
list_assets,
|
list_assets,
|
||||||
@@ -202,7 +203,12 @@ def frame_battery(
|
|||||||
alert_frame_name = ""
|
alert_frame_name = ""
|
||||||
with frame_locked(db, frame.id) as locked:
|
with frame_locked(db, frame.id) as locked:
|
||||||
locked.stats_battery_reports += 1
|
locked.stats_battery_reports += 1
|
||||||
if locked.battery_history and body.percent >= locked.battery_history[-1][1] + RECHARGE_JUMP_PCT:
|
# See RECHARGE_LOOKBACK: compared against the max of the last few
|
||||||
|
# reports, not just the single previous one, so a lone noisy dip
|
||||||
|
# can't make the next normal reading look like a recharge.
|
||||||
|
recent = locked.battery_history[-RECHARGE_LOOKBACK:]
|
||||||
|
recent_max = max((pct for _, pct in recent), default=None)
|
||||||
|
if recent_max is not None and body.percent >= recent_max + RECHARGE_JUMP_PCT:
|
||||||
# Percent jumped up meaningfully -- the battery was recharged
|
# Percent jumped up meaningfully -- the battery was recharged
|
||||||
# (or swapped). Start a fresh discharge cycle so runtime and
|
# (or swapped). Start a fresh discharge cycle so runtime and
|
||||||
# discharge-rate estimates never span a charge -- and let a
|
# discharge-rate estimates never span a charge -- and let a
|
||||||
|
|||||||
Reference in New Issue
Block a user