introduce battery.py
This commit is contained in:
parent
b3109e2241
commit
8423f6d2d7
@ -792,7 +792,7 @@ async def start_login_sequence():
|
||||
except: pass
|
||||
|
||||
if version.has_battery:
|
||||
from q1 import batt_idle_logout
|
||||
from battery import batt_idle_logout
|
||||
IMPT.start_task('b-idle', batt_idle_logout())
|
||||
|
||||
# maybe show a nickname before we do anything
|
||||
|
||||
175
shared/battery.py
Normal file
175
shared/battery.py
Normal file
@ -0,0 +1,175 @@
|
||||
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# battery.py - Q-specific code related to batteries and monitoring that.
|
||||
#
|
||||
# NOTE: Lots of hardware overlap with Mk4, so see mk4.py too!
|
||||
#
|
||||
from imptask import IMPT
|
||||
import uasyncio as asyncio
|
||||
from machine import Pin
|
||||
|
||||
# value must exist in battery_idle_timeout_chooser() choices
|
||||
DEFAULT_BATT_IDLE_TIMEOUT = const(30*60)
|
||||
|
||||
# 0..255 brightness value for when on batteries
|
||||
DEFAULT_BATT_BRIGHTNESS = const(200)
|
||||
|
||||
nbat_pin = Pin('NOT_BATTERY')
|
||||
|
||||
def setup_battery():
|
||||
# setup and monitor things.
|
||||
setup_adc()
|
||||
|
||||
IMPT.start_task('battery', batt_monitor_task())
|
||||
|
||||
#nbat_pin.irq(_nbatt_irq, Pin.IRQ_FALLING|Pin.IRQ_RISING)
|
||||
|
||||
async def batt_monitor_task():
|
||||
last_lvl = None
|
||||
|
||||
while 1:
|
||||
# slowly track battery level
|
||||
await asyncio.sleep(5)
|
||||
|
||||
lvl = get_batt_threshold()
|
||||
|
||||
if lvl != last_lvl:
|
||||
from glob import dis
|
||||
|
||||
dis.draw_status(bat=lvl)
|
||||
last_lvl = lvl
|
||||
|
||||
def setup_adc():
|
||||
# configure VREF source as internally generated
|
||||
import uctypes
|
||||
|
||||
VREF_LAYOUT = {
|
||||
"CSR": 0 | uctypes.UINT32,
|
||||
"CCR": 4 | uctypes.UINT32,
|
||||
}
|
||||
VREFBUF_CSR = 0x40010030
|
||||
|
||||
vref = uctypes.struct(VREFBUF_CSR, VREF_LAYOUT)
|
||||
vref.CSR = 0x05 # VRS=1, HIZ=0, ENVR=1 2.5v ref
|
||||
|
||||
# could delay here until reads back as 0x9 (VRR==1)
|
||||
# but no need
|
||||
|
||||
def get_batt_level():
|
||||
# return voltage from batteries, as a float
|
||||
# - will only work on battery power, else return None
|
||||
# - reads a bit low (3.3v in => 2.7v here)
|
||||
try:
|
||||
from machine import ADC, Pin
|
||||
except ImportError:
|
||||
# simulator
|
||||
return 2.99
|
||||
|
||||
if nbat_pin() == 1:
|
||||
# not getting power from batteries, so don't know level
|
||||
return None
|
||||
|
||||
adc = ADC(Pin('VIN_SENSE'))
|
||||
avg = sum(adc.read_u16() for i in range(13)) / 13.0
|
||||
|
||||
return round((avg / 65535.0) * 2.5 * 2, 1)
|
||||
|
||||
def get_batt_threshold():
|
||||
# return 0=empty, 1=low, 2=75% 3=full or None if no bat
|
||||
volts = get_batt_level()
|
||||
if volts is None:
|
||||
return None
|
||||
if volts <= 2.1:
|
||||
return 0
|
||||
if volts <= 3.0:
|
||||
return 1
|
||||
if volts <= 4.0:
|
||||
return 2
|
||||
return 3
|
||||
|
||||
def brightness_chooser():
|
||||
from glob import settings, dis
|
||||
|
||||
bright = settings.get('bright', DEFAULT_BATT_BRIGHTNESS)
|
||||
|
||||
ch = [ '25%', '50%', '60%', '70%', '80% (default)', '90%','100%']
|
||||
va = [ 64, 128, 153, 180, DEFAULT_BATT_BRIGHTNESS, 230, 255]
|
||||
|
||||
try:
|
||||
which = va.index(bright)
|
||||
except ValueError:
|
||||
which = DEFAULT_BATT_BRIGHTNESS
|
||||
|
||||
def _set(idx, text):
|
||||
settings.set('bright', va[idx])
|
||||
dis.set_lcd_brightness()
|
||||
|
||||
def _preview(idx):
|
||||
dis.set_lcd_brightness(tmp_override=va[idx])
|
||||
|
||||
return which, ch, _set, _preview
|
||||
|
||||
def battery_idle_timeout_chooser():
|
||||
from glob import settings
|
||||
|
||||
timeout = settings.get('batt_to', DEFAULT_BATT_IDLE_TIMEOUT) # in seconds
|
||||
|
||||
ch = [
|
||||
' 30 seconds',
|
||||
' 60 seconds',
|
||||
' 2 minutes',
|
||||
' 5 minutes',
|
||||
'10 minutes',
|
||||
'15 minutes',
|
||||
'30 minutes',
|
||||
' 1 hour',
|
||||
' 4 hours',
|
||||
' Never' ]
|
||||
va = [ 30, 60, 2*60, 5*60, 10*60, 15*60, 30*60,
|
||||
3600, 4*3600, 0 ]
|
||||
|
||||
try:
|
||||
which = va.index(timeout)
|
||||
except ValueError:
|
||||
which = 0
|
||||
|
||||
def _set(idx, text):
|
||||
settings.set('batt_to', va[idx])
|
||||
|
||||
return which, ch, _set
|
||||
|
||||
|
||||
async def batt_idle_logout():
|
||||
# long-running task to power down when idle too long.
|
||||
# - even before login
|
||||
import glob
|
||||
from uasyncio import sleep_ms
|
||||
from glob import settings
|
||||
import utime
|
||||
|
||||
while not glob.hsm_active:
|
||||
await sleep_ms(5000)
|
||||
|
||||
if get_batt_level() == None:
|
||||
# on USB power
|
||||
continue
|
||||
|
||||
last = glob.numpad.last_event_time
|
||||
if not last:
|
||||
continue
|
||||
|
||||
dt = utime.ticks_diff(utime.ticks_ms(), last)
|
||||
|
||||
# they may have changed setting recently
|
||||
timeout = settings.get('batt_to', DEFAULT_BATT_IDLE_TIMEOUT)*1000 # ms
|
||||
|
||||
if timeout and dt > timeout:
|
||||
# user has been idle for too long: do a logout (and powerdown)
|
||||
print("Batt Idle!")
|
||||
|
||||
from actions import logout_now
|
||||
await logout_now()
|
||||
return # not reached
|
||||
|
||||
|
||||
# EOF
|
||||
@ -34,10 +34,9 @@ else:
|
||||
hsm_feature = lambda: False
|
||||
make_users_menu = lambda: []
|
||||
|
||||
trick_pin_menu = TrickPinMenu.make_menu
|
||||
|
||||
# Battery related items
|
||||
if version.has_battery:
|
||||
from q1 import battery_idle_timeout_chooser, brightness_chooser
|
||||
from battery import battery_idle_timeout_chooser, brightness_chooser
|
||||
else:
|
||||
battery_idle_timeout_chooser = None
|
||||
brightness_chooser = None
|
||||
@ -93,7 +92,7 @@ with the Coldcard.''',
|
||||
LoginPrefsMenu = [
|
||||
# xxxxxxxxxxxxxxxx
|
||||
MenuItem('Change Main PIN', f=main_pin_changer),
|
||||
NonDefaultMenuItem('Trick PINs', 'tp', menu=trick_pin_menu),
|
||||
NonDefaultMenuItem('Trick PINs', 'tp', menu=TrickPinMenu.make_menu),
|
||||
NonDefaultMenuItem('Set Nickname', 'nick', prelogin=True, f=pick_nickname),
|
||||
NonDefaultMenuItem('Scramble Keys', 'rngk', prelogin=True, f=pick_scramble),
|
||||
NonDefaultMenuItem('Kill Key', 'kbtn', prelogin=True, f=pick_killkey),
|
||||
@ -349,9 +348,11 @@ EmptyWallet = [
|
||||
NormalSystem = [
|
||||
# xxxxxxxxxxxxxxxx
|
||||
MenuItem('Ready To Sign', f=ready2sign, shortcut='r'),
|
||||
MenuItem('Passphrase', f=start_b39_pw, predicate=bip39_passphrase_active, shortcut='p'),
|
||||
MenuItem('Scan Any QR Code', predicate=lambda: version.has_qr,
|
||||
shortcut=KEY_QR, f=scan_any_qr, arg=(False, True)),
|
||||
MenuItem('Passphrase', f=start_b39_pw, predicate=bip39_passphrase_active, shortcut='p'),
|
||||
MenuItem('NFC Tools', predicate=lambda: nfc_enabled() and version.has_qwerty,
|
||||
menu=NFCToolsMenu, shortcut=KEY_NFC),
|
||||
MenuItem('Start HSM Mode', f=start_hsm_menu_item, predicate=hsm_policy_available),
|
||||
MenuItem("Address Explorer", f=address_explore, shortcut='x'),
|
||||
MenuItem('Type Passwords', f=password_entry, shortcut='t',
|
||||
|
||||
@ -64,14 +64,10 @@ def get_sys_status():
|
||||
# Read current values for all status-bar items
|
||||
# - normally we update as we go along.
|
||||
# - return a dict
|
||||
from q1 import get_batt_threshold
|
||||
from battery import get_batt_threshold
|
||||
|
||||
rv = dict(shift=0, caps=0, symbol=0)
|
||||
b = get_batt_threshold()
|
||||
if b is None:
|
||||
rv['plugged'] = True
|
||||
else:
|
||||
rv['bat'] = b
|
||||
rv['bat'] = get_batt_threshold()
|
||||
|
||||
from stash import bip39_passphrase
|
||||
rv['bip39'] = int(bool(bip39_passphrase))
|
||||
@ -149,7 +145,7 @@ class Display:
|
||||
# Call when battery changes state, or if you want max for a bit (QR display)
|
||||
# - call w/o args to get back to state we're supposed to be in.
|
||||
from glob import settings
|
||||
from q1 import get_batt_threshold, DEFAULT_BATT_BRIGHTNESS
|
||||
from battery import get_batt_threshold, DEFAULT_BATT_BRIGHTNESS
|
||||
|
||||
if tmp_override is not None:
|
||||
self.dis.backlight.intensity(tmp_override)
|
||||
@ -182,11 +178,12 @@ class Display:
|
||||
|
||||
b_x = 290
|
||||
if 'bat' in kws:
|
||||
self.image(b_x, 0, 'bat_%d' % kws['bat'])
|
||||
self.set_lcd_brightness(True)
|
||||
if 'plugged' in kws:
|
||||
self.image(b_x, 0, 'plugged')
|
||||
self.set_lcd_brightness(False)
|
||||
if kws['bat'] is None:
|
||||
self.image(b_x, 0, 'plugged')
|
||||
self.set_lcd_brightness(False)
|
||||
else:
|
||||
self.image(b_x, 0, 'bat_%d' % kws['bat'])
|
||||
self.set_lcd_brightness(True)
|
||||
|
||||
if 'bip39' in kws:
|
||||
self.image(102, 0, 'bip39_%d' % kws['bip39'])
|
||||
@ -502,7 +499,7 @@ class Display:
|
||||
|
||||
def set_brightness(self, val):
|
||||
# - was only used by HSM ux code
|
||||
# - QR code display brightness could be done in show_qr_data()
|
||||
# - QR code display brightness is done in show_qr_data() now
|
||||
# - see self.set_lcd_brightness()
|
||||
return
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ assert not glob.dis, "main reimport"
|
||||
# this makes the GC run when larger objects are free in an attempt to reduce fragmentation.
|
||||
gc.threshold(4096)
|
||||
|
||||
# useful for debug: start serial port early
|
||||
# useful for debug: start serial port early, when possible
|
||||
try:
|
||||
from h import *
|
||||
import ckcc
|
||||
|
||||
@ -15,6 +15,7 @@ freeze_as_mpy('', [
|
||||
'ndef.py',
|
||||
'trick_pins.py',
|
||||
'ux_q1.py',
|
||||
'battery.py',
|
||||
], opt=0)
|
||||
|
||||
# Optimize data-like files, since no need to debug them.
|
||||
|
||||
132
shared/q1.py
132
shared/q1.py
@ -33,137 +33,9 @@ def init0():
|
||||
except: pass
|
||||
|
||||
try:
|
||||
setup_adc()
|
||||
import battery
|
||||
battery.setup_battery()
|
||||
#print('Batt volt: %s' % get_batt_level())
|
||||
except: pass
|
||||
|
||||
def setup_adc():
|
||||
# configure VREF source as internal 2.5v
|
||||
VREF_LAYOUT = {
|
||||
"CSR": 0 | uctypes.UINT32,
|
||||
"CCR": 4 | uctypes.UINT32,
|
||||
}
|
||||
VREFBUF_CSR = 0x40010030
|
||||
|
||||
vref = uctypes.struct(VREFBUF_CSR, VREF_LAYOUT)
|
||||
vref.CSR = 0x01 # VRS=0, HIZ=0, ENVR=1
|
||||
|
||||
# could delay here until reads back as 0x9 (VRR==1)
|
||||
# but no need
|
||||
|
||||
def get_batt_level():
|
||||
# return voltage from batteries, as a float
|
||||
# - will only work on battery power, else return None
|
||||
try:
|
||||
from machine import ADC, Pin
|
||||
except ImportError:
|
||||
# simulator
|
||||
return 2.99
|
||||
|
||||
if Pin('NOT_BATTERY')() == 1:
|
||||
# not getting power from batteries, so don't know level
|
||||
return None
|
||||
|
||||
adc = ADC(Pin('VIN_SENSE'))
|
||||
avg = sum(adc.read_u16() for i in range(10)) / 10.0
|
||||
|
||||
return round((avg / 65535.0) * 2.5 * 2, 2)
|
||||
|
||||
def get_batt_threshold():
|
||||
# return 0=empty, 1=low, 2=75% 3=full or None if no bat
|
||||
# TODO check these ranges
|
||||
volts = get_batt_level()
|
||||
if volts is None:
|
||||
return None
|
||||
if volts <= 3.0:
|
||||
return 0
|
||||
if volts <= 3.5:
|
||||
return 1
|
||||
return 3 if volts > 4.5 else 2
|
||||
|
||||
def brightness_chooser():
|
||||
from glob import settings, dis
|
||||
|
||||
bright = settings.get('bright', DEFAULT_BATT_BRIGHTNESS)
|
||||
|
||||
ch = [ '25%', '50%', '60%', '70%', '80% (default)', '90%','100%']
|
||||
va = [ 64, 128, 153, 180, DEFAULT_BATT_BRIGHTNESS, 230, 255]
|
||||
|
||||
try:
|
||||
which = va.index(bright)
|
||||
except ValueError:
|
||||
which = DEFAULT_BATT_BRIGHTNESS
|
||||
|
||||
def _set(idx, text):
|
||||
settings.set('bright', va[idx])
|
||||
dis.set_lcd_brightness()
|
||||
|
||||
def _preview(idx):
|
||||
dis.set_lcd_brightness(tmp_override=va[idx])
|
||||
|
||||
return which, ch, _set, _preview
|
||||
|
||||
def battery_idle_timeout_chooser():
|
||||
from glob import settings
|
||||
|
||||
timeout = settings.get('batt_to', DEFAULT_BATT_IDLE_TIMEOUT) # in seconds
|
||||
|
||||
ch = [
|
||||
' 30 seconds',
|
||||
' 60 seconds',
|
||||
' 2 minutes',
|
||||
' 5 minutes',
|
||||
'10 minutes',
|
||||
'15 minutes',
|
||||
'30 minutes',
|
||||
' 1 hour',
|
||||
' 4 hours',
|
||||
' Never' ]
|
||||
va = [ 30, 60, 2*60, 5*60, 10*60, 15*60, 30*60,
|
||||
3600, 4*3600, 0 ]
|
||||
|
||||
try:
|
||||
which = va.index(timeout)
|
||||
except ValueError:
|
||||
which = 0
|
||||
|
||||
def _set(idx, text):
|
||||
settings.set('batt_to', va[idx])
|
||||
|
||||
return which, ch, _set
|
||||
|
||||
|
||||
async def batt_idle_logout():
|
||||
# long-running task to power down when idle too long.
|
||||
# - even before login
|
||||
import glob
|
||||
from uasyncio import sleep_ms
|
||||
from glob import settings
|
||||
import utime
|
||||
|
||||
while not glob.hsm_active:
|
||||
await sleep_ms(5000)
|
||||
|
||||
if get_batt_level() == None:
|
||||
# on USB power
|
||||
continue
|
||||
|
||||
last = glob.numpad.last_event_time
|
||||
if not last:
|
||||
continue
|
||||
|
||||
dt = utime.ticks_diff(utime.ticks_ms(), last)
|
||||
|
||||
# they may have changed setting recently
|
||||
timeout = settings.get('batt_to', DEFAULT_BATT_IDLE_TIMEOUT)*1000 # ms
|
||||
|
||||
if timeout and dt > timeout:
|
||||
# user has been idle for too long: do a logout (and powerdown)
|
||||
print("Batt Idle!")
|
||||
|
||||
from actions import logout_now
|
||||
await logout_now()
|
||||
return # not reached
|
||||
|
||||
|
||||
# EOF
|
||||
|
||||
@ -35,6 +35,8 @@ def start():
|
||||
import atexit
|
||||
atexit.register(cleanup)
|
||||
|
||||
# XXX obsolete w/ Q changes?
|
||||
|
||||
os.chdir('./work')
|
||||
cc_cmd = ['../coldcard-mpy',
|
||||
'-X', 'heapsize=9m',
|
||||
|
||||
@ -33,7 +33,7 @@ if '--sflash' not in sys.argv:
|
||||
# Install various hacks and workarounds
|
||||
import mk4
|
||||
import sim_mk4
|
||||
import sim_q1
|
||||
import sim_battery
|
||||
import sim_psram
|
||||
import sim_vdisk
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ freeze_as_mpy('', [
|
||||
'os.py',
|
||||
'pyb.py',
|
||||
'sim_mk4.py',
|
||||
'sim_q1.py',
|
||||
'sim_battery.py',
|
||||
'sim_scanner.py',
|
||||
'sim_nfc.py',
|
||||
'sim_psram.py',
|
||||
|
||||
16
unix/variant/sim_battery.py
Normal file
16
unix/variant/sim_battery.py
Normal file
@ -0,0 +1,16 @@
|
||||
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# sim_battery.py - Simulate Q specific code related to batteries.
|
||||
#
|
||||
import battery
|
||||
|
||||
battery.setup_battery = lambda: None
|
||||
|
||||
battery.setup_adc = lambda: None
|
||||
|
||||
def mock_get_batt_level():
|
||||
return 3.69
|
||||
|
||||
battery.get_batt_level = mock_get_batt_level
|
||||
|
||||
# EOF
|
||||
@ -1,16 +0,0 @@
|
||||
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# sim_q14.py - Simulate Q1 specific code, not needed on other devices.
|
||||
#
|
||||
# - shared/q1.py calls mk4.init0 so no need to replace that
|
||||
#
|
||||
import q1
|
||||
|
||||
q1.setup_adc = lambda: None
|
||||
|
||||
def mock_get_batt_level():
|
||||
return 3.69
|
||||
|
||||
q1.get_batt_level = mock_get_batt_level
|
||||
|
||||
# EOF
|
||||
Loading…
Reference in New Issue
Block a user