Badger2040: JSON app state.
This commit is contained in:
parent
b8d5a3db75
commit
b85792f254
|
@ -20,10 +20,6 @@ LEFT_PADDING = 5
|
|||
NAME_PADDING = 20
|
||||
DETAIL_SPACING = 10
|
||||
|
||||
OVERLAY_BORDER = 40
|
||||
OVERLAY_SPACING = 20
|
||||
OVERLAY_TEXT_SIZE = 0.6
|
||||
|
||||
DEFAULT_TEXT = """mustelid inc
|
||||
H. Badger
|
||||
RP2040
|
||||
|
@ -63,42 +59,6 @@ def truncatestring(text, text_size, width):
|
|||
# Drawing functions
|
||||
# ------------------------------
|
||||
|
||||
# Draw an overlay box with a given message within it
|
||||
def draw_overlay(message, width, height, line_spacing, text_size):
|
||||
|
||||
# Draw a light grey background
|
||||
display.pen(12)
|
||||
display.rectangle((WIDTH - width) // 2, (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 = []
|
||||
line = ""
|
||||
appended_line = ""
|
||||
for word in words:
|
||||
if len(word) > 0:
|
||||
appended_line += " "
|
||||
appended_line += word
|
||||
if display.measure_text(appended_line, text_size) >= width:
|
||||
lines.append(line)
|
||||
appended_line = word
|
||||
else:
|
||||
line = appended_line
|
||||
if len(line) != 0:
|
||||
lines.append(line)
|
||||
|
||||
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], (WIDTH - length) // 2, (HEIGHT // 2) + current_line, text_size)
|
||||
|
||||
|
||||
# Draw the badge, including user text
|
||||
def draw_badge():
|
||||
display.pen(0)
|
||||
|
@ -203,10 +163,6 @@ detail2_title = truncatestring(detail2_title, DETAILS_TEXT_SIZE, TEXT_WIDTH)
|
|||
detail2_text = truncatestring(detail2_text, DETAILS_TEXT_SIZE,
|
||||
TEXT_WIDTH - DETAIL_SPACING - display.measure_text(detail2_title, DETAILS_TEXT_SIZE))
|
||||
|
||||
# Tell launcher to relaunch this app on wake
|
||||
if not display.woken():
|
||||
badger_os.state_save("badge")
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# Main program
|
||||
|
@ -216,9 +172,7 @@ draw_badge()
|
|||
|
||||
while True:
|
||||
if display.pressed(badger2040.BUTTON_A) or display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C) or display.pressed(badger2040.BUTTON_UP) or display.pressed(badger2040.BUTTON_DOWN):
|
||||
draw_overlay("To change the text, connect Badger2040 to a PC, load up Thonny, and modify badge.txt",
|
||||
WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, OVERLAY_TEXT_SIZE)
|
||||
display.update()
|
||||
badger_os.warning(display, "To change the text, connect Badger2040 to a PC, load up Thonny, and modify badge.txt")
|
||||
time.sleep(4)
|
||||
|
||||
draw_badge()
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import os
|
||||
import gc
|
||||
import time
|
||||
import json
|
||||
import machine
|
||||
import badger2040
|
||||
|
||||
STATE_FILE = "appstate.txt"
|
||||
|
||||
|
||||
def get_battery_level():
|
||||
# Battery measurement
|
||||
|
@ -47,55 +47,62 @@ def get_disk_usage():
|
|||
return f_total_size, f_used, f_free
|
||||
|
||||
|
||||
def state_app():
|
||||
try:
|
||||
with open(STATE_FILE, "r") as f:
|
||||
return f.readline().strip()
|
||||
except OSError:
|
||||
return None
|
||||
def state_running():
|
||||
state = {"running": "launcher"}
|
||||
state_load("launcher", state)
|
||||
return state["running"]
|
||||
|
||||
|
||||
def state_clear_running():
|
||||
state_modify("launcher", {"running": "launcher"})
|
||||
|
||||
|
||||
def state_set_running(app):
|
||||
state_modify("launcher", {"running": app})
|
||||
|
||||
|
||||
def state_launch():
|
||||
app = state_app()
|
||||
if app is not None:
|
||||
app = state_running()
|
||||
if app is not None and app != "launcher":
|
||||
launch("_" + app)
|
||||
|
||||
|
||||
def state_delete():
|
||||
def state_delete(app):
|
||||
try:
|
||||
os.remove(STATE_FILE)
|
||||
os.remove("{}_state.txt".format(app))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def state_save(title, *args):
|
||||
with open(STATE_FILE, "w") as f:
|
||||
f.write("{}\n".format(title))
|
||||
for arg in args:
|
||||
f.write("{}\n".format(arg))
|
||||
def state_save(app, data):
|
||||
with open("{}_state.txt".format(app), "w") as f:
|
||||
f.write(json.dumps(data))
|
||||
f.flush()
|
||||
|
||||
|
||||
def state_load(title, *defaults):
|
||||
data = []
|
||||
def state_modify(app, data):
|
||||
state = {}
|
||||
state_load(app, state)
|
||||
state.update(data)
|
||||
state_save(app, state)
|
||||
|
||||
|
||||
def state_load(app, defaults):
|
||||
try:
|
||||
with open(STATE_FILE, "r") as f:
|
||||
if f.readline().strip() != title:
|
||||
return defaults
|
||||
for default in defaults:
|
||||
t = type(default)
|
||||
if t is bool:
|
||||
data.append(f.readline().strip() == "True")
|
||||
else:
|
||||
data.append(t(f.readline().strip()))
|
||||
return data
|
||||
except OSError:
|
||||
return defaults
|
||||
data = json.loads(open("{}_state.txt".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):
|
||||
for k in locals().keys():
|
||||
if k not in ("gc", "file", "machine"):
|
||||
del locals()[k]
|
||||
state_set_running(file[1:])
|
||||
|
||||
gc.collect()
|
||||
|
||||
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||
|
@ -103,18 +110,66 @@ def launch(file):
|
|||
|
||||
def quit_to_launcher(pin):
|
||||
if button_a.value() and button_c.value():
|
||||
import os
|
||||
try:
|
||||
os.remove(STATE_FILE)
|
||||
except OSError:
|
||||
pass
|
||||
state_clear_running()
|
||||
time.sleep(0.1) # Needed to stop write fail
|
||||
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:
|
||||
__import__(file[1:]) # Try to import _[file] (drop underscore prefix)
|
||||
try:
|
||||
__import__(file[1:]) # Try to import _[file] (drop underscore prefix)
|
||||
except ImportError:
|
||||
__import__(file) # Failover to importing [_file]
|
||||
|
||||
except ImportError:
|
||||
__import__(file) # Failover to importing [_file]
|
||||
# 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()
|
||||
|
|
|
@ -2,7 +2,7 @@ import os
|
|||
import sys
|
||||
import time
|
||||
import badger2040
|
||||
from badger2040 import WIDTH, HEIGHT
|
||||
from badger2040 import HEIGHT
|
||||
import badger_os
|
||||
|
||||
|
||||
|
@ -50,40 +50,11 @@ except OSError:
|
|||
|
||||
|
||||
image = bytearray(int(296 * 128 / 8))
|
||||
current_image = 0
|
||||
show_info = True
|
||||
|
||||
|
||||
# Draw an overlay box with a given message within it
|
||||
def draw_overlay(message, width, height, line_spacing, text_size):
|
||||
|
||||
# Draw a light grey background
|
||||
display.pen(12)
|
||||
display.rectangle((WIDTH - width) // 2, (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], (WIDTH - length) // 2, (HEIGHT // 2) + current_line, text_size)
|
||||
state = {
|
||||
"current_image": 0,
|
||||
"show_info": True
|
||||
}
|
||||
|
||||
|
||||
def show_image(n):
|
||||
|
@ -92,7 +63,7 @@ def show_image(n):
|
|||
open("images/{}".format(file), "r").readinto(image)
|
||||
display.image(image)
|
||||
|
||||
if show_info:
|
||||
if state["show_info"]:
|
||||
name_length = display.measure_text(name, 0.5)
|
||||
display.pen(0)
|
||||
display.rectangle(0, HEIGHT - 21, name_length + 11, 21)
|
||||
|
@ -106,7 +77,7 @@ def show_image(n):
|
|||
y = int((128 / 2) - (TOTAL_IMAGES * 10 / 2) + (i * 10))
|
||||
display.pen(0)
|
||||
display.rectangle(x, y, 8, 8)
|
||||
if current_image != i:
|
||||
if state["current_image"] != i:
|
||||
display.pen(15)
|
||||
display.rectangle(x + 1, y + 1, 6, 6)
|
||||
|
||||
|
@ -116,40 +87,40 @@ def show_image(n):
|
|||
if TOTAL_IMAGES == 0:
|
||||
display.pen(15)
|
||||
display.clear()
|
||||
draw_overlay("To run this demo, create an /images directory on your device and upload some 1bit 296x128 pixel images.", WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, OVERLAY_TEXT_SIZE)
|
||||
display.update()
|
||||
badger_os.warning(display, "To run this demo, create an /images directory on your device and upload some 1bit 296x128 pixel images.")
|
||||
time.sleep(4.0)
|
||||
sys.exit()
|
||||
|
||||
|
||||
current_image, show_info = badger_os.state_load("image", 0, True)
|
||||
badger_os.state_load("image", state)
|
||||
|
||||
changed = not display.woken()
|
||||
|
||||
|
||||
while True:
|
||||
if display.pressed(badger2040.BUTTON_UP):
|
||||
if current_image > 0:
|
||||
current_image -= 1
|
||||
if state["current_image"] > 0:
|
||||
state["current_image"] -= 1
|
||||
changed = True
|
||||
if display.pressed(badger2040.BUTTON_DOWN):
|
||||
if current_image < TOTAL_IMAGES - 1:
|
||||
current_image += 1
|
||||
if state["current_image"] < TOTAL_IMAGES - 1:
|
||||
state["current_image"] += 1
|
||||
changed = True
|
||||
if display.pressed(badger2040.BUTTON_A):
|
||||
show_info = not show_info
|
||||
state["show_info"] = not state["show_info"]
|
||||
changed = True
|
||||
if display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C):
|
||||
display.pen(15)
|
||||
display.clear()
|
||||
draw_overlay("To add images connect Badger2040 to a PC, load up Thonny, and see readme.txt in images/", WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, 0.5)
|
||||
badger_os.warning(display, "To add images connect Badger2040 to a PC, load up Thonny, and see readme.txt in images/")
|
||||
display.update()
|
||||
print(current_image)
|
||||
print(state["current_image"])
|
||||
time.sleep(4)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
badger_os.state_save("image", current_image, show_info)
|
||||
show_image(current_image)
|
||||
badger_os.state_save("image", state)
|
||||
show_image(state["current_image"])
|
||||
changed = False
|
||||
|
||||
# Halt the Badger to save power, it will wake up if any of the front buttons are pressed
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import gc
|
||||
import time
|
||||
import math
|
||||
import machine
|
||||
|
@ -6,35 +7,33 @@ from badger2040 import WIDTH
|
|||
import launchericons
|
||||
import badger_os
|
||||
|
||||
# Reduce clock speed to 48MHz, that's fast enough!
|
||||
machine.freq(48000000)
|
||||
|
||||
if badger2040.pressed_to_wake(badger2040.BUTTON_A) and badger2040.pressed_to_wake(badger2040.BUTTON_C):
|
||||
# Pressing A and C together at start quits app
|
||||
badger_os.state_clear_running()
|
||||
badger2040.clear_pressed_to_wake()
|
||||
else:
|
||||
# Otherwise restore previously running app
|
||||
badger_os.state_launch()
|
||||
|
||||
# for e.g. 2xAAA batteries, try max 3.4 min 3.0
|
||||
MAX_BATTERY_VOLTAGE = 4.0
|
||||
MIN_BATTERY_VOLTAGE = 3.2
|
||||
|
||||
# Reduce clock speed to 48MHz, that's fast enough!
|
||||
machine.freq(48000000)
|
||||
|
||||
|
||||
# Restore previously running app
|
||||
try:
|
||||
# Pressing A and C together at start quits app
|
||||
if badger2040.pressed_to_wake(badger2040.BUTTON_A) and badger2040.pressed_to_wake(badger2040.BUTTON_C):
|
||||
badger_os.state_delete()
|
||||
else:
|
||||
if badger_os.state_app() != "launcher":
|
||||
badger_os.state_launch()
|
||||
except OSError:
|
||||
pass
|
||||
except ImportError:
|
||||
# Happens if appstate names an unknown app. Delete appstate and reset
|
||||
badger_os.state_delete()
|
||||
machine.reset()
|
||||
|
||||
|
||||
display = badger2040.Badger2040()
|
||||
display.led(128)
|
||||
|
||||
page, font_size, inverted = badger_os.state_load("launcher", 0, 1, False)
|
||||
changed = badger_os.state_app() != "launcher"
|
||||
state = {
|
||||
"page": 0,
|
||||
"font_size": 1,
|
||||
"inverted": False,
|
||||
"running": "launcher"
|
||||
}
|
||||
|
||||
badger_os.state_load("launcher", state)
|
||||
changed = state["running"] != "launcher"
|
||||
|
||||
icons = bytearray(launchericons.data())
|
||||
icons_width = 576
|
||||
|
@ -123,23 +122,23 @@ def render():
|
|||
display.pen(0)
|
||||
display.thickness(2)
|
||||
|
||||
max_icons = min(3, len(examples[(page * 3):]))
|
||||
max_icons = min(3, len(examples[(state["page"] * 3):]))
|
||||
|
||||
for i in range(max_icons):
|
||||
x = centers[i]
|
||||
label, icon = examples[i + (page * 3)]
|
||||
label, icon = examples[i + (state["page"] * 3)]
|
||||
label = label[1:].replace("_", " ")
|
||||
display.pen(0)
|
||||
display.icon(icons, icon, icons_width, 64, x - 32, 24)
|
||||
w = display.measure_text(label, font_sizes[font_size])
|
||||
display.text(label, x - int(w / 2), 16 + 80, font_sizes[font_size])
|
||||
w = display.measure_text(label, font_sizes[state["font_size"]])
|
||||
display.text(label, x - int(w / 2), 16 + 80, font_sizes[state["font_size"]])
|
||||
|
||||
for i in range(MAX_PAGE):
|
||||
x = 286
|
||||
y = int((128 / 2) - (MAX_PAGE * 10 / 2) + (i * 10))
|
||||
display.pen(0)
|
||||
display.rectangle(x, y, 8, 8)
|
||||
if page != i:
|
||||
if state["page"] != i:
|
||||
display.pen(15)
|
||||
display.rectangle(x + 1, y + 1, 6, 6)
|
||||
|
||||
|
@ -159,15 +158,20 @@ def render():
|
|||
def launch_example(index):
|
||||
while display.pressed(badger2040.BUTTON_A) or display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C) or display.pressed(badger2040.BUTTON_UP) or display.pressed(badger2040.BUTTON_DOWN):
|
||||
time.sleep(0.01)
|
||||
try:
|
||||
badger_os.launch(examples[(page * 3) + index][0])
|
||||
return True
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
file = examples[(state["page"] * 3) + index][0]
|
||||
|
||||
for k in locals().keys():
|
||||
if k not in ("gc", "file", "badger_os"):
|
||||
del locals()[k]
|
||||
|
||||
gc.collect()
|
||||
|
||||
badger_os.launch(file)
|
||||
|
||||
|
||||
def button(pin):
|
||||
global page, font_size, inverted, changed
|
||||
global changed
|
||||
changed = True
|
||||
|
||||
if not display.pressed(badger2040.BUTTON_USER): # User button is NOT held down
|
||||
|
@ -178,27 +182,27 @@ def button(pin):
|
|||
if pin == badger2040.BUTTON_C:
|
||||
launch_example(2)
|
||||
if pin == badger2040.BUTTON_UP:
|
||||
if page > 0:
|
||||
page -= 1
|
||||
if state["page"] > 0:
|
||||
state["page"] -= 1
|
||||
render()
|
||||
if pin == badger2040.BUTTON_DOWN:
|
||||
if page < MAX_PAGE - 1:
|
||||
page += 1
|
||||
if state["page"] < MAX_PAGE - 1:
|
||||
state["page"] += 1
|
||||
render()
|
||||
else: # User button IS held down
|
||||
if pin == badger2040.BUTTON_UP:
|
||||
font_size += 1
|
||||
if font_size == len(font_sizes):
|
||||
font_size = 0
|
||||
state["font_size"] += 1
|
||||
if state["font_size"] == len(font_sizes):
|
||||
state["font_size"] = 0
|
||||
render()
|
||||
if pin == badger2040.BUTTON_DOWN:
|
||||
font_size -= 1
|
||||
if font_size < 0:
|
||||
font_size = 0
|
||||
state["font_size"] -= 1
|
||||
if state["font_size"] < 0:
|
||||
state["font_size"] = 0
|
||||
render()
|
||||
if pin == badger2040.BUTTON_A:
|
||||
inverted = not inverted
|
||||
display.invert(inverted)
|
||||
state["inverted"] = not state["inverted"]
|
||||
display.invert(state["inverted"])
|
||||
render()
|
||||
|
||||
|
||||
|
@ -227,7 +231,7 @@ while True:
|
|||
button(badger2040.BUTTON_DOWN)
|
||||
|
||||
if changed:
|
||||
badger_os.state_save("launcher", page, font_size, inverted)
|
||||
badger_os.state_save("launcher", state)
|
||||
changed = False
|
||||
|
||||
display.halt()
|
||||
|
|
Loading…
Reference in New Issue