Skip to content

Commit 92a79a1

Browse files
authored
Merge pull request #3240 from videopixil/BLE_Beacon_Ears
BLE beacon ears
2 parents 1f97b68 + aeb2779 commit 92a79a1

5 files changed

Lines changed: 2126 additions & 0 deletions

File tree

BLE_Beacon_Ears/battery.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# SPDX-FileCopyrightText: 2026 Pedro Ruiz for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
'''Simple battery voltage monitor for the Beacon Ears.
5+
6+
Reads the BFF's voltage divider on board.A2 and exposes a `voltage`
7+
property. We keep this minimal: no state machine, no auto-warnings, no
8+
ambient rendering. The voltage is consumed only by the on-demand
9+
battery display in code.py when the user explicitly asks for it.
10+
11+
Note: the ESP32-S3 ADC reads voltages higher than actual (uncalibrated
12+
ADC gain error). We don't try to correct this - instead the thresholds
13+
in code.py are tuned to match what the QT Py actually reports for
14+
known battery states.
15+
'''
16+
# Target: Adafruit QT Py ESP32-S3 - the BLE Beacon Ears
17+
import time
18+
19+
import analogio
20+
import board
21+
22+
_ADC_FULL_SCALE = 65535
23+
_DIVIDER_RATIO = 2.0
24+
25+
_SAMPLES_PER_READ = 10
26+
_SAMPLE_INTERVAL_S = 5.0
27+
28+
29+
class BatteryMonitor:
30+
'''Polls battery voltage on A2. No state machine, no auto-rendering.'''
31+
32+
def __init__(self):
33+
try:
34+
self._pin = analogio.AnalogIn(board.A2)
35+
except (AttributeError, ValueError):
36+
self._pin = None
37+
self._last_read_t = 0.0
38+
self._last_voltage = None
39+
40+
def _read_voltage(self):
41+
'''Read and average N ADC samples, return raw volts on the cell.'''
42+
if self._pin is None:
43+
return None
44+
total = 0
45+
for _ in range(_SAMPLES_PER_READ):
46+
total += self._pin.value
47+
avg = total / _SAMPLES_PER_READ
48+
ref_v = self._pin.reference_voltage
49+
return (avg / _ADC_FULL_SCALE) * ref_v * _DIVIDER_RATIO
50+
51+
def update(self, force=False):
52+
'''Refresh voltage reading if enough time has passed.
53+
54+
If force=True, read voltage immediately regardless of interval.
55+
Useful when responding to a user trigger - we want the value
56+
shown to reflect the moment of the press, not 5 seconds ago.
57+
'''
58+
now = time.monotonic()
59+
if not force and now - self._last_read_t < _SAMPLE_INTERVAL_S:
60+
return
61+
self._last_read_t = now
62+
self._last_voltage = self._read_voltage()
63+
64+
@property
65+
def voltage(self):
66+
'''Last measured raw voltage in volts (None if unread/absent).'''
67+
return self._last_voltage

0 commit comments

Comments
 (0)