diff --git a/server/app/routers/common.py b/server/app/routers/common.py index 1fe5de4..8672dc1 100644 --- a/server/app/routers/common.py +++ b/server/app/routers/common.py @@ -21,7 +21,16 @@ logger = logging.getLogger(__name__) # Battery-history / estimate tuning (see /frame/battery and battery_estimate_s). 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 -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_DROP_PCT = 2 # ...and this much observed drop before estimating diff --git a/server/app/routers/device.py b/server/app/routers/device.py index 2b57d8b..7a53b82 100644 --- a/server/app/routers/device.py +++ b/server/app/routers/device.py @@ -28,6 +28,7 @@ from .common import ( BATTERY_HISTORY_MAX, BATTERY_LOG_MAX, RECHARGE_JUMP_PCT, + RECHARGE_LOOKBACK, immich_client_for, immich_creds, list_assets, @@ -202,7 +203,12 @@ def frame_battery( alert_frame_name = "" with frame_locked(db, frame.id) as locked: 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 # (or swapped). Start a fresh discharge cycle so runtime and # discharge-rate estimates never span a charge -- and let a