Module lib.tools.battery
Manage the battery
Expand source code
# Distributed under Pycameresp License
# Copyright (c) 2023 Remi BERTHOLET
# pylint:disable=consider-using-f-string
""" Manage the battery """
import uasyncio
import machine
import tools.jsonconfig
import tools.logger
import tools.tasking
import tools.support
if tools.support.battery():
try:
BROWNOUT_RESET = machine.BROWNOUT_RESET
except:
BROWNOUT_RESET = 6
MAX_BROWNOUT_RESET = 32
class BatteryConfig(tools.jsonconfig.JsonConfig):
""" Battery configuration """
def __init__(self):
""" Constructor """
tools.jsonconfig.JsonConfig.__init__(self)
# Battery monitoring
self.activated = False # Monitoring status
self.level_gpio = 12 # Monitoring GPIO
self.full_battery = 188 # 4.2V mesured with resistor 100k + 47k
self.empty_battery = 158 # 3.6V mesured with resistor 100k + 47k
# Force deep sleep if to many successive brown out reset detected
self.brownout_detection = True
self.brownout_count = 0
class Battery:
""" Manage the battery information """
config = None
level = [-2]
@staticmethod
def init():
""" Init battery class """
# If config not yet read
if Battery.config is None:
Battery.config = BatteryConfig()
# If config failed to read
if Battery.config.load() is False:
# Write default config
Battery.config.save()
@staticmethod
def get_level():
""" Return the battery level between 0% to 100% (0%=3.6V 100%=4.2V).
For the ESP32CAM with Gpio12, the value can be read only before the open of camera and SD card.
The voltage always smaller than 1.5V otherwise the card does not boot (JTAG detection I think).
This GPIO 12 of the ESP32CAM not have a pull up resistor, it is the only one which allows the ADC measurement.
I had to patch the micropython firmware to be able to read the GPIO 12."""
Battery.init()
# If battery level not yet read at start
if Battery.level[0] == -2:
level = -1
try:
adc = machine.ADC(machine.Pin(Battery.config.level_gpio))
adc.atten(machine.ADC.ATTN_11DB)
adc.width(machine.ADC.WIDTH_9BIT)
count = 3
val = 0
for i in range(count):
val += adc.read()
# If battery level pin not connected
if val < (Battery.config.empty_battery * count) // 2:
level = -1
else:
# Compute battery level
level = Battery.calc_percent(val/count, Battery.config)
if level < 0.:
level = 0
elif level > 100.:
level = 100
else:
level = int(level)
tools.logger.syslog("Battery level %d %% (%d)"%(level, int(val/count)))
except Exception as err:
tools.logger.syslog(err,"Cannot read battery status")
Battery.level[0] = level
return Battery.level[0]
@staticmethod
def is_activated():
""" Indicates if the battery management activated """
Battery.init()
return Battery.config.activated
@staticmethod
def calc_percent(x, config):
""" Calc the percentage of battery according to the configuration """
x1 = config.full_battery
y1 = 100
x2 = config.empty_battery
y2 = 0
a = (y1 - y2)/(x1 - x2)
b = y1 - (a * x1)
y = a*x + b
return y
@staticmethod
def protect():
""" Protect the battery """
Battery.init()
Battery.keep_reset_cause()
if Battery.manage_level() or Battery.is_too_many_brownout():
# Too many brownout reset
# Slow deepsleep during 1 hour
if Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60:
tools.logger.syslog("Sleep 1 minute")
machine.deepsleep(600*1000)
# Slow deepsleep during one day
elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24:
tools.logger.syslog("Sleep 1 hour")
machine.deepsleep(3600*1000)
# Slow deepsleep during three days
elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8:
tools.logger.syslog("Sleep 3 hours")
machine.deepsleep(3*3600*1000)
# Slow deepsleep during one week
elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8 + 7:
tools.logger.syslog("Sleep 24 hours")
machine.deepsleep(24*3600*1000)
# Deepsleep infinite
else:
tools.logger.syslog("Sleep infinite")
machine.deepsleep()
@staticmethod
def manage_level():
""" Checks if the battery level is sufficient.
If the battery is too low, we enter indefinite deep sleep to protect the battery """
deepsleep = False
Battery.init()
if Battery.config.activated:
# Can only be done once at boot before start the camera and sd card
battery_level = Battery.get_level()
# If the battery is too low
if battery_level > 5 or battery_level < 0:
battery_protect = False
else:
battery_protect = True
# Case the battery has not enough current and must be protected
if battery_protect:
deepsleep = True
tools.logger.syslog("Battery too low %d %%"%battery_level)
return deepsleep
@staticmethod
def keep_reset_cause():
""" Keep reset cause """
causes = {
machine.PWRON_RESET : "Power on",
machine.HARD_RESET : "Hard",
machine.WDT_RESET : "Watch dog",
machine.DEEPSLEEP_RESET : "Deep sleep",
machine.SOFT_RESET : "Soft",
BROWNOUT_RESET : "Brownout",
}.setdefault(machine.reset_cause(), "%d"%machine.reset_cause())
tools.logger.syslog("%s Start %s"%('-'*10,'-'*10), display=False)
tools.logger.syslog("%s reset"%causes)
@staticmethod
def is_too_many_brownout():
""" Checks the number of brownout reset """
deepsleep = False
Battery.config.refresh()
if Battery.config.brownout_detection:
# If the reset can probably due to insufficient battery
if machine.reset_cause() == BROWNOUT_RESET:
Battery.config.brownout_count += 1
else:
Battery.config.brownout_count = 0
Battery.config.save()
# if the number of consecutive brownout resets is too high
if Battery.config.brownout_count > MAX_BROWNOUT_RESET:
# Battery too low, save the battery status
tools.logger.syslog("Too many successive brownout reset %d"%Battery.config.brownout_count)
deepsleep = True
return deepsleep
@staticmethod
def reset_brownout():
""" Reset brownout counter if wifi connected """
if Battery.config is not None:
if Battery.config.brownout_count > 0:
Battery.config.brownout_count = 0
Battery.config.save()
@staticmethod
async def task():
""" Internal periodic task """
if Battery.config is not None:
Battery.config.refresh()
else:
Battery.protect()
await uasyncio.sleep(11)
return True
@staticmethod
def start(**kwargs):
""" Start battery monitoring task """
if tools.support.battery():
Battery.protect()
tools.tasking.Tasks.create_monitor(Battery.task)
else:
tools.logger.syslog("Battery management not supported on this hardware")
Classes
class Battery-
Manage the battery information
Expand source code
class Battery: """ Manage the battery information """ config = None level = [-2] @staticmethod def init(): """ Init battery class """ # If config not yet read if Battery.config is None: Battery.config = BatteryConfig() # If config failed to read if Battery.config.load() is False: # Write default config Battery.config.save() @staticmethod def get_level(): """ Return the battery level between 0% to 100% (0%=3.6V 100%=4.2V). For the ESP32CAM with Gpio12, the value can be read only before the open of camera and SD card. The voltage always smaller than 1.5V otherwise the card does not boot (JTAG detection I think). This GPIO 12 of the ESP32CAM not have a pull up resistor, it is the only one which allows the ADC measurement. I had to patch the micropython firmware to be able to read the GPIO 12.""" Battery.init() # If battery level not yet read at start if Battery.level[0] == -2: level = -1 try: adc = machine.ADC(machine.Pin(Battery.config.level_gpio)) adc.atten(machine.ADC.ATTN_11DB) adc.width(machine.ADC.WIDTH_9BIT) count = 3 val = 0 for i in range(count): val += adc.read() # If battery level pin not connected if val < (Battery.config.empty_battery * count) // 2: level = -1 else: # Compute battery level level = Battery.calc_percent(val/count, Battery.config) if level < 0.: level = 0 elif level > 100.: level = 100 else: level = int(level) tools.logger.syslog("Battery level %d %% (%d)"%(level, int(val/count))) except Exception as err: tools.logger.syslog(err,"Cannot read battery status") Battery.level[0] = level return Battery.level[0] @staticmethod def is_activated(): """ Indicates if the battery management activated """ Battery.init() return Battery.config.activated @staticmethod def calc_percent(x, config): """ Calc the percentage of battery according to the configuration """ x1 = config.full_battery y1 = 100 x2 = config.empty_battery y2 = 0 a = (y1 - y2)/(x1 - x2) b = y1 - (a * x1) y = a*x + b return y @staticmethod def protect(): """ Protect the battery """ Battery.init() Battery.keep_reset_cause() if Battery.manage_level() or Battery.is_too_many_brownout(): # Too many brownout reset # Slow deepsleep during 1 hour if Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60: tools.logger.syslog("Sleep 1 minute") machine.deepsleep(600*1000) # Slow deepsleep during one day elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24: tools.logger.syslog("Sleep 1 hour") machine.deepsleep(3600*1000) # Slow deepsleep during three days elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8: tools.logger.syslog("Sleep 3 hours") machine.deepsleep(3*3600*1000) # Slow deepsleep during one week elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8 + 7: tools.logger.syslog("Sleep 24 hours") machine.deepsleep(24*3600*1000) # Deepsleep infinite else: tools.logger.syslog("Sleep infinite") machine.deepsleep() @staticmethod def manage_level(): """ Checks if the battery level is sufficient. If the battery is too low, we enter indefinite deep sleep to protect the battery """ deepsleep = False Battery.init() if Battery.config.activated: # Can only be done once at boot before start the camera and sd card battery_level = Battery.get_level() # If the battery is too low if battery_level > 5 or battery_level < 0: battery_protect = False else: battery_protect = True # Case the battery has not enough current and must be protected if battery_protect: deepsleep = True tools.logger.syslog("Battery too low %d %%"%battery_level) return deepsleep @staticmethod def keep_reset_cause(): """ Keep reset cause """ causes = { machine.PWRON_RESET : "Power on", machine.HARD_RESET : "Hard", machine.WDT_RESET : "Watch dog", machine.DEEPSLEEP_RESET : "Deep sleep", machine.SOFT_RESET : "Soft", BROWNOUT_RESET : "Brownout", }.setdefault(machine.reset_cause(), "%d"%machine.reset_cause()) tools.logger.syslog("%s Start %s"%('-'*10,'-'*10), display=False) tools.logger.syslog("%s reset"%causes) @staticmethod def is_too_many_brownout(): """ Checks the number of brownout reset """ deepsleep = False Battery.config.refresh() if Battery.config.brownout_detection: # If the reset can probably due to insufficient battery if machine.reset_cause() == BROWNOUT_RESET: Battery.config.brownout_count += 1 else: Battery.config.brownout_count = 0 Battery.config.save() # if the number of consecutive brownout resets is too high if Battery.config.brownout_count > MAX_BROWNOUT_RESET: # Battery too low, save the battery status tools.logger.syslog("Too many successive brownout reset %d"%Battery.config.brownout_count) deepsleep = True return deepsleep @staticmethod def reset_brownout(): """ Reset brownout counter if wifi connected """ if Battery.config is not None: if Battery.config.brownout_count > 0: Battery.config.brownout_count = 0 Battery.config.save() @staticmethod async def task(): """ Internal periodic task """ if Battery.config is not None: Battery.config.refresh() else: Battery.protect() await uasyncio.sleep(11) return True @staticmethod def start(**kwargs): """ Start battery monitoring task """ if tools.support.battery(): Battery.protect() tools.tasking.Tasks.create_monitor(Battery.task) else: tools.logger.syslog("Battery management not supported on this hardware")Class variables
var configvar level
Static methods
def calc_percent(x, config)-
Calc the percentage of battery according to the configuration
Expand source code
@staticmethod def calc_percent(x, config): """ Calc the percentage of battery according to the configuration """ x1 = config.full_battery y1 = 100 x2 = config.empty_battery y2 = 0 a = (y1 - y2)/(x1 - x2) b = y1 - (a * x1) y = a*x + b return y def get_level()-
Return the battery level between 0% to 100% (0%=3.6V 100%=4.2V). For the ESP32CAM with Gpio12, the value can be read only before the open of camera and SD card. The voltage always smaller than 1.5V otherwise the card does not boot (JTAG detection I think). This GPIO 12 of the ESP32CAM not have a pull up resistor, it is the only one which allows the ADC measurement. I had to patch the micropython firmware to be able to read the GPIO 12.
Expand source code
@staticmethod def get_level(): """ Return the battery level between 0% to 100% (0%=3.6V 100%=4.2V). For the ESP32CAM with Gpio12, the value can be read only before the open of camera and SD card. The voltage always smaller than 1.5V otherwise the card does not boot (JTAG detection I think). This GPIO 12 of the ESP32CAM not have a pull up resistor, it is the only one which allows the ADC measurement. I had to patch the micropython firmware to be able to read the GPIO 12.""" Battery.init() # If battery level not yet read at start if Battery.level[0] == -2: level = -1 try: adc = machine.ADC(machine.Pin(Battery.config.level_gpio)) adc.atten(machine.ADC.ATTN_11DB) adc.width(machine.ADC.WIDTH_9BIT) count = 3 val = 0 for i in range(count): val += adc.read() # If battery level pin not connected if val < (Battery.config.empty_battery * count) // 2: level = -1 else: # Compute battery level level = Battery.calc_percent(val/count, Battery.config) if level < 0.: level = 0 elif level > 100.: level = 100 else: level = int(level) tools.logger.syslog("Battery level %d %% (%d)"%(level, int(val/count))) except Exception as err: tools.logger.syslog(err,"Cannot read battery status") Battery.level[0] = level return Battery.level[0] def init()-
Init battery class
Expand source code
@staticmethod def init(): """ Init battery class """ # If config not yet read if Battery.config is None: Battery.config = BatteryConfig() # If config failed to read if Battery.config.load() is False: # Write default config Battery.config.save() def is_activated()-
Indicates if the battery management activated
Expand source code
@staticmethod def is_activated(): """ Indicates if the battery management activated """ Battery.init() return Battery.config.activated def is_too_many_brownout()-
Checks the number of brownout reset
Expand source code
@staticmethod def is_too_many_brownout(): """ Checks the number of brownout reset """ deepsleep = False Battery.config.refresh() if Battery.config.brownout_detection: # If the reset can probably due to insufficient battery if machine.reset_cause() == BROWNOUT_RESET: Battery.config.brownout_count += 1 else: Battery.config.brownout_count = 0 Battery.config.save() # if the number of consecutive brownout resets is too high if Battery.config.brownout_count > MAX_BROWNOUT_RESET: # Battery too low, save the battery status tools.logger.syslog("Too many successive brownout reset %d"%Battery.config.brownout_count) deepsleep = True return deepsleep def keep_reset_cause()-
Keep reset cause
Expand source code
@staticmethod def keep_reset_cause(): """ Keep reset cause """ causes = { machine.PWRON_RESET : "Power on", machine.HARD_RESET : "Hard", machine.WDT_RESET : "Watch dog", machine.DEEPSLEEP_RESET : "Deep sleep", machine.SOFT_RESET : "Soft", BROWNOUT_RESET : "Brownout", }.setdefault(machine.reset_cause(), "%d"%machine.reset_cause()) tools.logger.syslog("%s Start %s"%('-'*10,'-'*10), display=False) tools.logger.syslog("%s reset"%causes) def manage_level()-
Checks if the battery level is sufficient. If the battery is too low, we enter indefinite deep sleep to protect the battery
Expand source code
@staticmethod def manage_level(): """ Checks if the battery level is sufficient. If the battery is too low, we enter indefinite deep sleep to protect the battery """ deepsleep = False Battery.init() if Battery.config.activated: # Can only be done once at boot before start the camera and sd card battery_level = Battery.get_level() # If the battery is too low if battery_level > 5 or battery_level < 0: battery_protect = False else: battery_protect = True # Case the battery has not enough current and must be protected if battery_protect: deepsleep = True tools.logger.syslog("Battery too low %d %%"%battery_level) return deepsleep def protect()-
Protect the battery
Expand source code
@staticmethod def protect(): """ Protect the battery """ Battery.init() Battery.keep_reset_cause() if Battery.manage_level() or Battery.is_too_many_brownout(): # Too many brownout reset # Slow deepsleep during 1 hour if Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60: tools.logger.syslog("Sleep 1 minute") machine.deepsleep(600*1000) # Slow deepsleep during one day elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24: tools.logger.syslog("Sleep 1 hour") machine.deepsleep(3600*1000) # Slow deepsleep during three days elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8: tools.logger.syslog("Sleep 3 hours") machine.deepsleep(3*3600*1000) # Slow deepsleep during one week elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8 + 7: tools.logger.syslog("Sleep 24 hours") machine.deepsleep(24*3600*1000) # Deepsleep infinite else: tools.logger.syslog("Sleep infinite") machine.deepsleep() def reset_brownout()-
Reset brownout counter if wifi connected
Expand source code
@staticmethod def reset_brownout(): """ Reset brownout counter if wifi connected """ if Battery.config is not None: if Battery.config.brownout_count > 0: Battery.config.brownout_count = 0 Battery.config.save() def start(**kwargs)-
Start battery monitoring task
Expand source code
@staticmethod def start(**kwargs): """ Start battery monitoring task """ if tools.support.battery(): Battery.protect() tools.tasking.Tasks.create_monitor(Battery.task) else: tools.logger.syslog("Battery management not supported on this hardware") async def task()-
Internal periodic task
Expand source code
@staticmethod async def task(): """ Internal periodic task """ if Battery.config is not None: Battery.config.refresh() else: Battery.protect() await uasyncio.sleep(11) return True
class BatteryConfig-
Battery configuration
Constructor
Expand source code
class BatteryConfig(tools.jsonconfig.JsonConfig): """ Battery configuration """ def __init__(self): """ Constructor """ tools.jsonconfig.JsonConfig.__init__(self) # Battery monitoring self.activated = False # Monitoring status self.level_gpio = 12 # Monitoring GPIO self.full_battery = 188 # 4.2V mesured with resistor 100k + 47k self.empty_battery = 158 # 3.6V mesured with resistor 100k + 47k # Force deep sleep if to many successive brown out reset detected self.brownout_detection = True self.brownout_count = 0Ancestors
- tools.jsonconfig.JsonConfig