184 lines
5.0 KiB
Python
184 lines
5.0 KiB
Python
"""Keep track of app state in persistent flash storage."""
|
|
|
|
import os
|
|
import gc
|
|
import time
|
|
import json
|
|
import machine
|
|
import badger2040
|
|
|
|
|
|
def get_battery_level():
|
|
# Battery measurement
|
|
vbat_adc = machine.ADC(badger2040.PIN_BATTERY)
|
|
vref_adc = machine.ADC(badger2040.PIN_1V2_REF)
|
|
vref_en = machine.Pin(badger2040.PIN_VREF_POWER)
|
|
vref_en.init(machine.Pin.OUT)
|
|
vref_en.value(0)
|
|
|
|
# Enable the onboard voltage reference
|
|
vref_en.value(1)
|
|
|
|
# Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries
|
|
vdd = 1.24 * (65535 / vref_adc.read_u16())
|
|
vbat = (
|
|
(vbat_adc.read_u16() / 65535) * 3 * vdd
|
|
) # 3 in this is a gain, not rounding of 3.3V
|
|
|
|
# Disable the onboard voltage reference
|
|
vref_en.value(0)
|
|
|
|
# Convert the voltage to a level to display onscreen
|
|
return vbat
|
|
|
|
|
|
def get_disk_usage():
|
|
# f_bfree and f_bavail should be the same?
|
|
# f_files, f_ffree, f_favail and f_flag are unsupported.
|
|
f_bsize, f_frsize, f_blocks, f_bfree, _, _, _, _, _, f_namemax = os.statvfs("/")
|
|
|
|
f_total_size = f_frsize * f_blocks
|
|
f_total_free = f_bsize * f_bfree
|
|
f_total_used = f_total_size - f_total_free
|
|
|
|
f_used = 100 / f_total_size * f_total_used
|
|
f_free = 100 / f_total_size * f_total_free
|
|
|
|
return f_total_size, f_used, f_free
|
|
|
|
|
|
def state_running():
|
|
state = {"running": "launcher"}
|
|
state_load("launcher", state)
|
|
return state["running"]
|
|
|
|
|
|
def state_clear_running():
|
|
running = state_running()
|
|
state_modify("launcher", {"running": "launcher"})
|
|
return running != "launcher"
|
|
|
|
|
|
def state_set_running(app):
|
|
state_modify("launcher", {"running": app})
|
|
|
|
|
|
def state_launch():
|
|
app = state_running()
|
|
if app is not None and app != "launcher":
|
|
launch("_" + app)
|
|
|
|
|
|
def state_delete(app):
|
|
try:
|
|
os.remove("/state/{}.json".format(app))
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def state_save(app, data):
|
|
try:
|
|
with open("/state/{}.json".format(app), "w") as f:
|
|
f.write(json.dumps(data))
|
|
f.flush()
|
|
except OSError:
|
|
import os
|
|
try:
|
|
os.stat("/state")
|
|
except OSError:
|
|
os.mkdir("/state")
|
|
state_save(app, data)
|
|
|
|
|
|
def state_modify(app, data):
|
|
state = {}
|
|
state_load(app, state)
|
|
state.update(data)
|
|
state_save(app, state)
|
|
|
|
|
|
def state_load(app, defaults):
|
|
try:
|
|
data = json.loads(open("/state/{}.json".format(app), "r").read())
|
|
if type(data) is dict:
|
|
defaults.update(data)
|
|
return True
|
|
except (OSError, ValueError):
|
|
pass
|
|
|
|
state_save(app, defaults)
|
|
return False
|
|
|
|
|
|
def launch(file):
|
|
state_set_running(file[1:])
|
|
|
|
gc.collect()
|
|
|
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
|
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
|
|
|
def quit_to_launcher(pin):
|
|
if button_a.value() and button_c.value():
|
|
machine.reset()
|
|
|
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)
|
|
button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)
|
|
|
|
try:
|
|
try:
|
|
__import__(file[1:]) # Try to import _[file] (drop underscore prefix)
|
|
except ImportError:
|
|
__import__(file) # Failover to importing [_file]
|
|
|
|
except ImportError:
|
|
# If the app doesn't exist, notify the user
|
|
warning(None, "Could not launch: " + file[1:])
|
|
time.sleep(4.0)
|
|
except Exception as e:
|
|
# If the app throws an error, catch it and display!
|
|
print(e)
|
|
warning(None, str(e))
|
|
time.sleep(4.0)
|
|
|
|
# If the app exits or errors, do not relaunch!
|
|
state_clear_running()
|
|
machine.reset() # Exit back to launcher
|
|
|
|
|
|
# Draw an overlay box with a given message within it
|
|
def warning(display, message, width=badger2040.WIDTH - 40, height=badger2040.HEIGHT - 40, line_spacing=20, text_size=0.6):
|
|
if display is None:
|
|
display = badger2040.Badger2040()
|
|
display.led(128)
|
|
|
|
# Draw a light grey background
|
|
display.pen(12)
|
|
display.rectangle((badger2040.WIDTH - width) // 2, (badger2040.HEIGHT - height) // 2, width, height)
|
|
|
|
# Take the provided message and split it up into
|
|
# lines that fit within the specified width
|
|
words = message.split(" ")
|
|
|
|
lines = []
|
|
current_line = ""
|
|
for word in words:
|
|
if display.measure_text(current_line + word + " ", text_size) < width:
|
|
current_line += word + " "
|
|
else:
|
|
lines.append(current_line.strip())
|
|
current_line = word + " "
|
|
lines.append(current_line.strip())
|
|
|
|
display.pen(0)
|
|
display.thickness(2)
|
|
|
|
# Display each line of text from the message, centre-aligned
|
|
num_lines = len(lines)
|
|
for i in range(num_lines):
|
|
length = display.measure_text(lines[i], text_size)
|
|
current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2
|
|
display.text(lines[i], (badger2040.WIDTH - length) // 2, (badger2040.HEIGHT // 2) + current_line, text_size)
|
|
|
|
display.update()
|