From b277a0357da2d6c3278fbc956a14db04ec360cdb Mon Sep 17 00:00:00 2001 From: maxelweb Date: Sun, 19 May 2024 21:59:13 +0200 Subject: [PATCH] feat: add fork from https://github.com/oneearedrabbit/badger-system-ii --- README.md | 58 +- case/.gitkeep | 1 + case/README.md | 28 + case/badger2040_bottom.stl | Bin 0 -> 153984 bytes case/badger2040_top_mirrored.stl | Bin 0 -> 69084 bytes src/.gitkeep | 1 + src/README.md | 46 ++ src/badge_app.py | 96 +++ src/badger_os.py | 183 +++++ src/badges/badge.bin | Bin 0 -> 1152 bytes src/badges/badge.png | Bin 0 -> 2386 bytes src/badges/badge.txt | 5 + src/fortune/cookie.txt | 1308 ++++++++++++++++++++++++++++++ src/fortune_app.py | 121 +++ src/images/background.bin | 1 + src/images/background.png | Bin 0 -> 716 bytes src/images/census.bin | Bin 0 -> 128 bytes src/images/census.png | Bin 0 -> 192 bytes src/images/clippy.bin | Bin 0 -> 512 bytes src/images/clippy.png | Bin 0 -> 788 bytes src/launcher.py | 86 ++ src/main.py | 1 + src/qr_app.py | 134 +++ src/qrcodes/qrcode.txt | 8 + src/widgets.py | 321 ++++++++ 25 files changed, 2396 insertions(+), 2 deletions(-) create mode 100644 case/.gitkeep create mode 100644 case/README.md create mode 100644 case/badger2040_bottom.stl create mode 100644 case/badger2040_top_mirrored.stl create mode 100644 src/.gitkeep create mode 100644 src/README.md create mode 100644 src/badge_app.py create mode 100644 src/badger_os.py create mode 100644 src/badges/badge.bin create mode 100644 src/badges/badge.png create mode 100644 src/badges/badge.txt create mode 100644 src/fortune/cookie.txt create mode 100644 src/fortune_app.py create mode 100644 src/images/background.bin create mode 100644 src/images/background.png create mode 100644 src/images/census.bin create mode 100644 src/images/census.png create mode 100644 src/images/clippy.bin create mode 100644 src/images/clippy.png create mode 100644 src/launcher.py create mode 100644 src/main.py create mode 100644 src/qr_app.py create mode 100644 src/qrcodes/qrcode.txt create mode 100644 src/widgets.py diff --git a/README.md b/README.md index d810cea..79d9381 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,57 @@ -# personal-badger +# Badger2040 System II -Personal Badger 2040 (from Pimoroni) \ No newline at end of file +Tired of boring badges that just blend in with the crowd? You want to show off your quirky personality and love of retro technology? You are looking for a fun project to bond with your engineering team? Look no further than the programmable e-ink badge! + +https://kruzenshtern.org/the-e-ink-badge-the-coolest-badge-you-didnt-know-you-needed/ + +![image](https://user-images.githubusercontent.com/198995/219474204-890703d2-fb32-4299-a39b-2d434ac3f215.png) + +## Source code + +Don't expect much, but it works (tm). See in /src. + +## Components + +1. Badger 2040: https://shop.pimoroni.com/products/badger-2040 +2. Coin Cell Battery Holder: https://www.adafruit.com/product/783 +3. 2x CR2032: https://www.amazon.com/gp/product/B078GC5K81/ +4. 4x M2 8mm bolts: https://www.amazon.com/gp/product/B01BNIHG0E/ +5. 3D-printed case: see /case folder. I was using Prusament PLA: https://www.prusa3d.com/product/prusament-pla-ms-pink-blend-970g/ + +## A quick glance at assembly steps + +1. 3D print top and bottom panels. +2. Disassemble & attach a 2xCR2032 battery holder +3. Assemble the badge, use hot glue whenever applicable +4. Upload pimonori-badger2040-micropython bootloader +5. Upload Python scripts + +## License & acknowledgements + +Inspiration sources for the case: + +- https://kaenner.de/badger2040 (CC4) +- https://www.thingiverse.com/thing:5320100/files (CC4) + +I ended up using an OpenSCAD blueprint by usedbytes to get measurements of the device. Reconstructed/synthesized a new case in Fusion360. Noticeable changes: +- A different battery holder / back pannel to simplify the assembly to some extent +- Two coin cells vs three +- Added hidden buttons inspired by Kц╓nner's design +- Back panel is inspired by Kц╓nner's design too, I like that connectors are accessible +- Battery toggle button +- Any mistakes are exclusively mine + +Source code: +- To a large extent based on pimoroni Badger2040 OS example: https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/examples/badger2040 (MIT) +- Font & rendering: custom code, but it is not worth extracting it to a standalone app/library. Let's stick with MIT for simplicity too. + +Assets: +- Font: 16bfZX https://www.pentacom.jp/pentacom/bitfontmaker2/gallery/?id=246 (Public domain) +- Clippy: hand-drawn, I think the character is trademarked by Microsoft though. +- Other assets: hand-drawn + +What does it mean in terms of license? I guess, it is good for a hobby project. Hire a lawyer to check if it is good for commercial use. + +## From + +The folks at [Census](http://getcensus.com) originally put this together. Have data? We'll sync your data warehouse with your CRM and the customer success apps critical to your team. diff --git a/case/.gitkeep b/case/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/case/.gitkeep @@ -0,0 +1 @@ + diff --git a/case/README.md b/case/README.md new file mode 100644 index 0000000..e5fb803 --- /dev/null +++ b/case/README.md @@ -0,0 +1,28 @@ +# 3D printed case for Badger2040 + +A 3D-printed case has a slot for two CR2032 batteries. Badger 2040 runs +on RPi 2040. It is energy efficient, and batteries should last for a +long time. Also comes with a battery toggle switch to shut everything down. + +The device is approximately 12mm thick, see below. I lost my digital +caliper, may replace the side shot later. + +## Assembly + +Steps: +- 3D print top and bottom panels. PLA plastic is good enough. +- Disassemble & attach a 2xCR2032 battery holder + https://www.adafruit.com/product/783. +- Assemble the badge, use hot glue whenever applicable + +## Known bugs + +- A 3D model has minor misalignments that I did not bother to fix, + e.g. a LED hole is 0.1-0.2mm off to the left. + +## Photos + +![image](https://user-images.githubusercontent.com/198995/219485328-1d66b0d7-2c20-477a-9fee-6ed1c341de5f.png) + +![image](https://user-images.githubusercontent.com/198995/221962577-9ab8847a-aaa5-4b5d-9cbb-67dff24e7f34.jpeg) + diff --git a/case/badger2040_bottom.stl b/case/badger2040_bottom.stl new file mode 100644 index 0000000000000000000000000000000000000000..abba823604f1d5121fca8b09c21fedc86bf13cb3 GIT binary patch literal 153984 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

CUw5HF3qnLW0&J3}F1(j3*jXC`~xdbFDM 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%0A-28%y&_Qyu3khuAE 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

BAHrSJT=G zvYb~<&P^do~V^^rwlrLoIn zS(KQSMSI=JqCF5)1_^J$DcX|{t!MSlXt%Vr`9#!yRyko-^k>m7kr;B$=rb$x*Gkl0 zy6QvAomMQj6z!?bJznXrl}PO)C#(ip|9-_{Pq^{RK=FG#;q8jy;dR35hpqK#t~D<8 z|EZAhBc*Tfv=X$6-%aVIFX|+MO8oLkPrr#n-+4Z$kci(j>6Kr*Awer!HS(TXbKK-S zJj0B)D%w zURES%#a$ROqULdVhPLNK)k|-mdCn^;B;q%1n)+~}CP6FPJEQ8gzC{;GQ56#L+dLs+ z@_g`Ew6Z-ws$T2cfWe0;qCz5m`KVX6C=#@a-^424 zkE%(~%AcC`=VYWhVmqfo!u{D-#?-!zpq1w-eRrvjL`}s@SHGM@-+5V4A>s93d=p9D zSJNkyb!};WiW0T*-}#o1^>9>pT#w`On@A*RGCb{7Ltlc1IRwl6!7ph6<v=)`d+!$JrxpoM|r>p30lQn?0e;2_f$yW9pwQZ zBxohAE7ZB|j?em_LIUq7=RV|p#O7GE!W+i}ghz?SuGgl1$AWR3M+p@YUQg)@6_S610k6uILptSd*X?_t?wNiVBJN1rGT}%e*`i zYZA2LUV*s}DkS1}L3-(%FJ9+030mQO?)-Nh1cd3w&ULyGO{dZFh)>KH~Ix5e(jt1#iw8B-`0Kq!&{z}Is@fYGvpIXT3L&~aA zNK8KBa2NBqcX((;tfxUAO%0OpBc=VqQ?&C~w2FH?%lBmldY`P$X@z<4K+dU-f=}6|H1O9!kBqHsBS#@24M*>;CLZ0;~@ni&mb$a&L7K zw8HwsKz&dl;U!(}txkegSVtM~L4|}zOxaVDpcU4G27FK<;c;B{?IdW0b*%v(R7iO1 zDn^5f1g$3T+r2N*GV$I=>wtY_-lC|G@ZPSB5)!oXJ~%~U9s82n*4ypwlCD!%GS=!! zI`32EXGMjCu2U;*cN!!mf7V}-pq1VKsCst)14K=NR`!Hx)w3r|K~z*o@NP|>4-&M} z=M&|~@fl(Iph7~QnpDD`7bQW3ggr0nPe+rWLLz?xn*FfR7mhn{Nx0!_*OwqP$9wl>XQ?+;@c59L4^eGt4~hQif?)31Qil|_arB% zkl?-h$q8EV9h#h=LPF9@B${NDZr=H^o?(DUR(zT=_d$inHaJShRp^*=DkS*yUhq-35wzk{f%#cc;q!^~ zofA|@@CnMv30m>F&Wxz3@Cj4;&WMT%iRLNY=J{QVD=O_o^E9pz9G&@w3TFoSSy3T@ rInl)A1g&s&pA%fmm;k}+!2tr-gOd>hb5X1m2$5DlCVZA@cai@eVtQ~P literal 0 HcmV?d00001 diff --git a/case/badger2040_top_mirrored.stl b/case/badger2040_top_mirrored.stl new file mode 100644 index 0000000000000000000000000000000000000000..286d8e9526c78782c1de3b3729d48f57b67d5f10 GIT binary patch literal 69084 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 YnnUOQvh2V6?%1=F2~^=T$uaSN0rSiWx&QzG literal 0 HcmV?d00001 diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..25bbdd3 --- /dev/null +++ b/src/README.md @@ -0,0 +1,46 @@ +# Badger2040, Apple Macintosh 1984 + +## Overview + +This is a custom UI for Badger 2040 that I built for the Census +engineering offsite in 2022. I aimed to recreate the iconic 1984 +Macintosh UI in a compact design. + +## Known bugs + +I had to cut some corners, so that I could finish the assembly of a small batch +before the event. Here is a list of known bugs: +- If you open the same application twice (e.g. open QR app, open Badge + app, and open QR app again), the device will restart and show a + welcome screen. I shouldn't have tried to build a multi app + experience in the first place. IMO, it is too much to do to + implement this right in Python. I think if it was Lisp environment, + it would have been easier. +- I forgot to adjust the battery voltage to calculate the battery + level. The device may show that a battery is empty or it is not + plugged in. + +## Upload steps + +- Upload pimonori-badger2040-micropython bootloader +- Upload Python scripts + +## Edit text + +- Badge: badges/badge.txt +- QR: qrcodes/qrcode.txt +- Clippy: fortune/cookie.txt + +## Edit images + +1. Clone https://github.com/pimoroni/pimoroni-pico +2. `python3 examples/badger2040/image_converter/convert.py --binary image.png image.bin` +3. cp image.bin badges/badge.bin + +## User Guide + +- A button opens Badge app +- B button opens QR app +- C button opens Special app. Up/Down buttons randomly load a new + quote +- A + C buttons open Welcome screen diff --git a/src/badge_app.py b/src/badge_app.py new file mode 100644 index 0000000..3c1d2ae --- /dev/null +++ b/src/badge_app.py @@ -0,0 +1,96 @@ +import badger2040 +import os +import badger_os +from widgets import draw_window, pprint, ptitle, plength, button, draw_ui + +IMAGE_WIDTH = 96 +IMAGE_HEIGHT = 96 +DELTA = 0 + +# Check that the badges directory exists, if not, make it +try: + os.mkdir("badges") +except OSError: + pass + +# Load all available badge Code Files +try: + CODES = [f for f in os.listdir("/badges") if f.endswith(".txt")] + TOTAL_CODES = len(CODES) +except OSError: + pass + +print(f'There are {TOTAL_CODES} badges available:') +for codename in CODES: + print(f'File: {codename}') + +display = badger2040.Badger2040() +display.update_speed(badger2040.UPDATE_NORMAL) + +state = { + "running": "badge_app", +} + +def draw_badge(n): + draw_window(display, 6, 26, 182, 94, " Badge ") + + file = CODES[n] + codetext = open("badges/{}".format(file), "r") + + lines = codetext.read().strip().split("\n") + name_text = lines.pop(0) + title_text = lines.pop(0) + company_text = lines.pop(0) + github_text = lines.pop(0) + badge_path = lines.pop(0) + + ptitle(display, name_text, 15, 44, 0) + + if len(github_text.strip()) > 0: + # github icon + display.image(bytearray((0x3c,0x00,0xa5,0x81,0x81,0xc3,0x66,0x84)), 8, 8, 18, 86) + display.image(bytearray((0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfe)), 8, 8, 18, 78) + display.image(bytearray((0x03,0x03,0x03,0x03,0x03,0x02,0x01,0x00)), 8, 8, 10, 86) + display.image(bytearray((0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x01)), 8, 8, 10, 78) + + pprint(display, title_text, 15, 60, 0) + pprint(display, company_text, 15, 72, 0) + pprint(display, github_text, 30, 84, 0) + + badge_dat = bytearray(int(IMAGE_WIDTH * IMAGE_HEIGHT / 8)) + open(f"badges/{badge_path}", "rb").readinto(badge_dat) + + display.image(badge_dat, IMAGE_WIDTH, IMAGE_HEIGHT, 194, 26) + + +def render(): + display.pen(15) + display.clear() + + draw_ui(display, "Badge") + + draw_badge(0) + + display.update() + +changed = not badger2040.woken_by_button() + +while True: + if display.pressed(badger2040.BUTTON_A): + changed = True +# button(display, badger2040.BUTTON_A) + if display.pressed(badger2040.BUTTON_B): + changed = True + button(display, badger2040.BUTTON_B) + if display.pressed(badger2040.BUTTON_C): + changed = True + button(display, badger2040.BUTTON_C) + + if changed: + display.led(128) + render() + badger_os.state_save("badges", state) + display.led(0) + changed = False + + display.halt() diff --git a/src/badger_os.py b/src/badger_os.py new file mode 100644 index 0000000..1d4825d --- /dev/null +++ b/src/badger_os.py @@ -0,0 +1,183 @@ +"""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! + print("System error, soft reset!") + 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() diff --git a/src/badges/badge.bin b/src/badges/badge.bin new file mode 100644 index 0000000000000000000000000000000000000000..5dac00a898fa106dfe3e2a3ddbbe117a86eb3f3d GIT binary patch literal 1152 zcmZuxU1%It6rODsO)RCG6ty8XnY7zL9|}Um2#s0uLz9Bg;)5W*q@?15(G?fn;2I|} zu^6yyvVpX5O|DX*_+UjNDKu@n}5S5dwJw0`w+-*S2U12D0 zm_yGT$ekP%4qQ)uUdm6lNn)@CQdC2M^G($#8>V=nsO}3Vq1e1ZMBEF zpgtkYUFOi^TDfwfkm>07$Ui3d74mUf0=rJND+iLz7KhJvw6up^!oY~xUs|sVn3{w- zQx^0MaHO)#fqBCi-s?J`T23O6c%(}R>Y3|X2TME4pfN?ci$k^kex=-tguC1PfhJM7 zpCzB`FV20?4QrZmF+N(XorYRUGp8o^dZ;V}z1YP>9L4teKLoSW3|YuG-}z9)8%G&> zv3#K66%kJjFr*gsp3tjo^5+tUOZLlO#_Ac?N|?4b&U`Ud60+VR{*51VPknxZhh8Fl zdGk=^={@|)r-Z+kS2b~ZNG1rKz<+XPCqy5 z?}yG4e_NRqW*K0&(wuw8gT7io5}$u>E@=1wub)k;E?^!5{-&O7-I2}_?j~H+3+E~U z-0Py<@p>ylwRnVK3zw8ufGZiaSO+VBz7qylM5h08EqL>EEz$dDO^Avyz;c()RX4%g zDOud>jPBpofY}=cx5L{SS{0iq-t;v1TIJW5e;M57!&+L{{Mhn*YM{m2>A*HVuYOV@ zyWg53{w7ja^{sEWc?ow}-8MsBEs0o$-#GHDr%5>w?)SBgS?Vnh#}a2d8%cwh1r`16 zU}ZgFAviE!_k?5`4jeu+-&`$;cWJJ(*T!q6nCrL>oi_LMN#X@4<+b>=y^@8!(O_Nt z8u#=CjTnvdVL6c@md=WecF8P|>F)|#tum>?L?a`L1Cplaf^!?ii;}&cA==gNhRbEr zA$JWGzT75RYTeWGP{E{sVTPdd4}8#^)O H{crpSR@D9| literal 0 HcmV?d00001 diff --git a/src/badges/badge.png b/src/badges/badge.png new file mode 100644 index 0000000000000000000000000000000000000000..08e9e6f883578a21f6d33c5ea30a8bd819859a9e GIT binary patch literal 2386 zcmV-Y39a^tP)Px;3`s;mRCt{2UE8i3Hw=`E{QobXJ~RRpW^R3Nv^H#?A?H<3sR}Xo;UVlAPsN>Q6b?p@= z>%0?l?e)y+o{(S_QZ@LF`&-5sLTG#1CaHrn`iUX;V>&@ zve9=Wi-ku7F46;8`|jS6g(nv;>wu2s zr<<-yxR8ZHN0rs3sH;q7?Fl1ep1*!iJa`OKh$S?WB~SKVS@1f>ZG4UI_2QV!bzOHr zN>8^28pGz$3rG$?jje~lV!X2OG(@0_P*9$ghPWimCLU@FF{YGD)`?s+hh360h;XO_Bkryb&rQ|0_JwCDgRtQoz=d6VtM%QIK;be|O zTpg|9xIfa*<-OF4Dr`42O(vaKio79l?<96Jz0mV6`$5mUhEgFfAY}?j0c2I`u$1+$ zmYmcH$_tQQ&ic_lQ#Oa>{jtsfh1e<5n7=CLiZb<%B~oPoDm1BQ4p$1XgmH9pgFHzC|R2y^+Z70X2gk{1eVfHMk09~9-l35bN=NNPdjFu`r>wAUtQrm@?Qp8co)Jhbc zWAUBMcXdmmJJUtU+)cg{Z5G6yp~FCjsFX?>%T7{M-k5}qM1M5bUC?$bB2{22kgdOP$wL39DMh%kyK%s?@hI%dAB>$_*QAY&Jmgf});L0wj>65d&G z-MnHkEVQ_KK&@R5y_@_~#*=y@j2NR?33sbQ72lb1iprw-ZVj|vqBmoE-8jF_iz;D- zF7IWGgjKCoMkg1nYt*sM6H=y=8fnFLA;ttUqy-V0m&Wq?9L4uKuTuoNeXVknvD-O7 zExt1kadgG|oy9Y&?j%+OHzqNxw2pfGZ%4Z%imF_dr0e_@F1gnqCqLN>dY_IT;|(kb zg$9<$SaGGSm=#G$zF=!7YoRfWwaqbb&!fqLcEI8<`kv~74lp4A>~nWNE=eQLM!K~_ z5OOocvFBp*fa(F%I-!OKCcAouvb;JEeeOW<`uf}%RO|uDS`s(yLe&M1kti!gtTutn!%kpd_dGebtrek``BPeq!}2jxL}RG7#lNGAcR@i(P~& z%61Kgjp^1HZ_5FQsW3}NiIWv;$wpnsa(X*issMC|X=oShU>rBDIf1!$!Z4~#bXds+ zG7n;GtaHIvldy{2YR(v1c~#{q4^)?el6yLz>7w4Vb*@}MC#Iuhi^iynNe++6>(I|z z#iUy6Y6wC}7NNecp2H4Ug)gNOTJtt)K0=}GkxFnyl-3ph06ZVzT&mE}RM(KFH5bJn>s%|IJLTts; zjAupH>npBbKsVNQdogvh?46SRWFoHmI?sKi6;UmLSq4kS^*fu3K6lyyI~J?txMJ_o zI)Euw6{>KD=PV3SLFa`#JsX8i`=sKL3vh>~#wX=FO3+DSzJ2f&X^vX)m*L7Sg;S`U1z>dZ2xO~cN z7)9O5Q7JxJh>daHA+5}0eWv6(I?1}0=-F<Z_8luJ~_BLj;)mxb;>X*17mnwA+|nKN+pMtxW7B7T?*MTs)Wuy+6|rPgr08y zCXLD6W*n=!l}tAV$$7jP$1dVlK1h-)(`S{DgcG%Hcg=~n(NJayiIOLml+w-48fmeH z*jvT%{~?p2)3H?VSW;GCVJk(=S+yExa}r$rKV&*{Zgv*Coy(+tSgm&rDLefCkr0%! zm|Joa*(~%Ve4m9y6wlGcDE*|Tj7xtYv(t3DiKG{l^np4#0@3M|jm>eb-<+HFVv_M4 zoY2=KWVy<@zDtX-1g+nkd)m$HUP$7ZousT7QMb68>zpR|_&4XCEOfISos4lD{YIX; z#e$Ol2m4-eGlX>*SV9cuvl`M=S65M6g?0#XJstMlVaJWT zw7AY=ds}I#3Nfq@>s0$u2ufSar*XB5bdD>Q97}W0cddVnkcPUBrn7{1!m*$=WUTK_ zioFwD`3D?a__=Kn!_J>bLm7ZnIr!*5KQrvLx|07*qoM6N<$ Efw3X8STTEv$aOH+T2$+}rjuXYPMD-!o{-?Qh%O z$9ttc^N$XgwCQh{o~rTZ<+>W4H}gI>Cp}vRV*Ob#!!n!OD`?woAZuqPkY#4Ck#_D@ z)1)bHL99Fw>nwlQ%+1>tsHE-&v1WrMj4xRZFgYaQmD9Or8v$u6{1- HoD!MG2?H!@u~!_&A+$y}c~bpa2`K8Mppm; literal 0 HcmV?d00001 diff --git a/src/images/clippy.bin b/src/images/clippy.bin new file mode 100644 index 0000000000000000000000000000000000000000..ef4c1f373d21942fc34afdb99ab4c7674d28bfcb GIT binary patch literal 512 zcmaKpv1-FG6h)t;C=MpXUIN8S4Ba}FE+LcEAzK2Wl#tP*$NWL`DgJ;A89RoK?UFfD zKEYGaQfS|OqEHC+prhlfcQuIUpDzm%7JK3JDr^ejX#}b^6>h~%n81@zdER7UcVSM# zu47hRh2o0(p8d&N@ZO1=O9t0>`SB;FUau^f1MPBVuH67s9xP{Xx2Yx1wa46WDD$R! zta;w}c#pTNWLyI3WAsJzbM$pm^8tOfE2ddzTlhyRz8p31&tijoeieV;!`1T!uG&t# trs(v?sU`H$iO^4nUq5g!HA?bV-NGNN1IMWc(=UMRi_paPi*@o-)^F8GIjR5v literal 0 HcmV?d00001 diff --git a/src/images/clippy.png b/src/images/clippy.png new file mode 100644 index 0000000000000000000000000000000000000000..0ae1c8e0de3dda357b54716f7e9b39170e8b2427 GIT binary patch literal 788 zcmV+v1MB>WP)Px%&PhZ;RCt`_T5XamAqd1*dj!4z0rd{-FY*``6vwFzWcyF5;z!bLgN#l=M1((# zfA9VS@5Y}O;OpIgU>Bv7WuKj>PUQe1PXw(`k4UdMvm`-IenybDd#I%d0ZHB>R!Vub zdtDGd1UUKD+H_U#gg|HqcvoKHqa47=cuDOzAgllu=^445zJ!nh?5yBw%xbQ10z65c zw_ANe32=w}>HJ{?@RHYdf}L7U{!Zv^5Wr84_uJV%!#IF|h?m;FwRU?R&)857kSj&2 z*II+#`_^3Ek~oY5ESu6QdY!-tr*nXW6s5HWV~nX-+L)PxLOu?!EUzWD)flTiOb8G> z@h%gbXH4&14T(?!+(~vj&_oEG17_&GLn#Gntw*AT5x^7o1d9J!>$Kg2P$963F~;#6 zsReifX#}7Wnd~k1eA#Y6x+aVO);9Gdv_dJ20093*mX|{? zNoFN<3IR?IJXYgFg@DAJwXyCCErb?e_nJ$N^&qqWrzb)lgeiqJ@Dm~a9l)C4M>@cJ zMNWM@2rWP<<;S+>%?Z3unu`$$EeCjSiFuz|t0`P)0q!_JyHp)xe1-$O?+M5nfE=J9 zv;dj|==+%y 1: + for i in range(TOTAL_CODES): + x = 286 + y = int((128 / 2) - (TOTAL_CODES * 10 / 2) + (i * 10)) + display.pen(0) + display.rectangle(x, y, 8, 8) + if state["current_qr"] != i: + display.pen(15) + display.rectangle(x + 1, y + 1, 6, 6) + +def render(): + display.pen(15) + display.clear() + + draw_ui(display, "QR") + + draw_qr_file(state["current_qr"]) + + display.update() + +badger_os.state_load("qrcodes", state) +changed = not badger2040.woken_by_button() + +while True: + if TOTAL_CODES > 1: + if display.pressed(badger2040.BUTTON_UP): + if state["current_qr"] > 0: + state["current_qr"] -= 1 + changed = True + + if display.pressed(badger2040.BUTTON_DOWN): + if state["current_qr"] < TOTAL_CODES - 1: + state["current_qr"] += 1 + changed = True + + if display.pressed(badger2040.BUTTON_A): + changed = True + button(display, badger2040.BUTTON_A) + if display.pressed(badger2040.BUTTON_B): + changed = True +# button(display, badger2040.BUTTON_B) + if display.pressed(badger2040.BUTTON_C): + changed = True + button(display, badger2040.BUTTON_C) + + if changed: + display.led(128) + render() + badger_os.state_save("qrcodes", state) + display.led(0) + changed = False + + # Halt the Badger to save power, it will wake up if any of the front buttons are pressed + display.halt() diff --git a/src/qrcodes/qrcode.txt b/src/qrcodes/qrcode.txt new file mode 100644 index 0000000..095097c --- /dev/null +++ b/src/qrcodes/qrcode.txt @@ -0,0 +1,8 @@ +https://getcensus.com/ +Census +* the leading reverse ETL +* no more CSV files +* sync data in real-time + +Scan this code to learn +more about Census. \ No newline at end of file diff --git a/src/widgets.py b/src/widgets.py new file mode 100644 index 0000000..ae96c08 --- /dev/null +++ b/src/widgets.py @@ -0,0 +1,321 @@ +import badger_os +import badger2040 +import time +import gc +from badger2040 import WIDTH + +# for e.g. 2xAAA batteries, try max 3.4 min 3.0 +MAX_BATTERY_VOLTAGE = 3.4 +MIN_BATTERY_VOLTAGE = 3.0 + +font_table = { + ' ': (0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ), # 0x20 32 + '!': (0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x03,0x00,0x00 ), # 0x21 33 + '"': (0x00,0x00,0x1B,0x1B,0x12,0x00,0x00,0x00,0x00,0x00 ), # 0x22 34 + '#': (0x00,0x00,0x36,0x7F,0x36,0x36,0x7F,0x36,0x00,0x00 ), # 0x23 35 + '$': (0x00,0x0C,0x3E,0x0F,0x1E,0x3C,0x3F,0x1E,0x0C,0x00 ), # 0x24 36 + '%': (0x00,0x00,0x63,0x33,0x18,0x0C,0x66,0x63,0x00,0x00 ), # 0x25 37 + '&': (0x00,0x00,0x1E,0x33,0x1E,0x3F,0x1B,0x3E,0x00,0x00 ), # 0x26 38 + "'": (0x00,0x00,0x03,0x03,0x02,0x00,0x00,0x00,0x00,0x00 ), # 0x27 39 + '(': (0x00,0x06,0x03,0x03,0x03,0x03,0x03,0x03,0x06,0x00 ), # 0x28 40 + ')': (0x00,0x03,0x06,0x06,0x06,0x06,0x06,0x06,0x03,0x00 ), # 0x29 41 + '*': (0x00,0x00,0x00,0x33,0x1E,0x1E,0x33,0x00,0x00,0x00 ), # 0x2A 42 + '+': (0x00,0x00,0x0C,0x0C,0x3F,0x0C,0x0C,0x00,0x00,0x00 ), # 0x2B 43 + ',': (0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x02,0x00 ), # 0x2C 44 + '-': (0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,0x00 ), # 0x2D 45 + '.': (0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x00,0x00 ), # 0x2E 46 + '/': (0x00,0x40,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x00 ), # 0x2F 47 + '0': (0x00,0x00,0x1E,0x33,0x33,0x33,0x33,0x1E,0x00,0x00 ), # 0x30 48 + '1': (0x00,0x00,0x06,0x07,0x06,0x06,0x06,0x0F,0x00,0x00 ), # 0x31 49 + '2': (0x00,0x00,0x1E,0x33,0x30,0x1E,0x03,0x3F,0x00,0x00 ), # 0x32 50 + '3': (0x00,0x00,0x1E,0x33,0x18,0x30,0x33,0x1E,0x00,0x00 ), # 0x33 51 + '4': (0x00,0x00,0x18,0x1C,0x1E,0x1B,0x3F,0x18,0x00,0x00 ), # 0x34 52 + '5': (0x00,0x00,0x3F,0x03,0x1F,0x30,0x33,0x1E,0x00,0x00 ), # 0x35 53 + '6': (0x00,0x00,0x1E,0x03,0x1F,0x33,0x33,0x1E,0x00,0x00 ), # 0x36 54 + '7': (0x00,0x00,0x3F,0x30,0x18,0x0C,0x0C,0x0C,0x00,0x00 ), # 0x37 55 + '8': (0x00,0x00,0x1E,0x33,0x1E,0x33,0x33,0x1E,0x00,0x00 ), # 0x38 56 + '9': (0x00,0x00,0x1E,0x33,0x33,0x3E,0x30,0x1E,0x00,0x00 ), # 0x39 57 + ':': (0x00,0x00,0x00,0x03,0x03,0x00,0x03,0x03,0x00,0x00 ), # 0x3A 58 + ';': (0x00,0x00,0x00,0x03,0x03,0x00,0x03,0x03,0x02,0x00 ), # 0x3B 59 + '<': (0x00,0x18,0x0C,0x06,0x03,0x06,0x0C,0x18,0x00,0x00 ), # 0x3C 60 + '=': (0x00,0x00,0x00,0x3F,0x00,0x3F,0x00,0x00,0x00,0x00 ), # 0x3D 61 + '>': (0x00,0x03,0x06,0x0C,0x18,0x0C,0x06,0x03,0x00,0x00 ), # 0x3E 62 + '?': (0x00,0x00,0x1E,0x33,0x18,0x0C,0x00,0x0C,0x00,0x00 ), # 0x3F 63 + '@': (0x7E,0xC3,0x3B,0xEF,0xEF,0xFB,0xC3,0x7E,0x00,0x00 ), # 0x40 64 + 'A': (0x00,0x00,0x1E,0x33,0x33,0x3F,0x33,0x33,0x00,0x00 ), # 0x41 65 + 'B': (0x00,0x00,0x1F,0x33,0x1F,0x33,0x33,0x1F,0x00,0x00 ), # 0x42 66 + 'C': (0x00,0x00,0x1E,0x33,0x03,0x03,0x33,0x1E,0x00,0x00 ), # 0x43 67 + 'D': (0x00,0x00,0x1F,0x33,0x33,0x33,0x33,0x1F,0x00,0x00 ), # 0x44 68 + 'E': (0x00,0x00,0x3F,0x03,0x0F,0x03,0x03,0x3F,0x00,0x00 ), # 0x45 69 + 'F': (0x00,0x00,0x3F,0x03,0x0F,0x03,0x03,0x03,0x00,0x00 ), # 0x46 70 + 'G': (0x00,0x00,0x1E,0x33,0x03,0x3B,0x33,0x1E,0x00,0x00 ), # 0x47 71 + 'H': (0x00,0x00,0x33,0x33,0x3F,0x33,0x33,0x33,0x00,0x00 ), # 0x48 72 + 'I': (0x00,0x00,0x0F,0x06,0x06,0x06,0x06,0x0F,0x00,0x00 ), # 0x49 73 + 'J': (0x00,0x00,0x30,0x30,0x30,0x30,0x33,0x1E,0x00,0x00 ), # 0x4A 74 + 'K': (0x00,0x00,0x33,0x1B,0x0F,0x0F,0x1B,0x33,0x00,0x00 ), # 0x4B 75 + 'L': (0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x3F,0x00,0x00 ), # 0x4C 76 + 'M': (0x00,0x00,0xC3,0xE7,0xFF,0xDB,0xC3,0xC3,0x00,0x00 ), # 0x4D 77 + 'N': (0x00,0x00,0x33,0x37,0x3F,0x3B,0x33,0x33,0x00,0x00 ), # 0x4E 78 + 'O': (0x00,0x00,0x1E,0x33,0x33,0x33,0x33,0x1E,0x00,0x00 ), # 0x4F 79 + 'P': (0x00,0x00,0x1F,0x33,0x33,0x1F,0x03,0x03,0x00,0x00 ), # 0x50 80 + 'Q': (0x00,0x00,0x1E,0x33,0x33,0x33,0x1B,0x36,0x00,0x00 ), # 0x51 81 + 'R': (0x00,0x00,0x1F,0x33,0x33,0x1F,0x1B,0x33,0x00,0x00 ), # 0x52 82 + 'S': (0x00,0x00,0x1E,0x03,0x1E,0x30,0x33,0x1E,0x00,0x00 ), # 0x53 83 + 'T': (0x00,0x00,0x3F,0x0C,0x0C,0x0C,0x0C,0x0C,0x00,0x00 ), # 0x54 84 + 'U': (0x00,0x00,0x33,0x33,0x33,0x33,0x33,0x1E,0x00,0x00 ), # 0x55 85 + 'V': (0x00,0x00,0x33,0x33,0x33,0x33,0x1E,0x0C,0x00,0x00 ), # 0x56 86 + 'W': (0x00,0x00,0xC3,0xDB,0xDB,0xDB,0xDB,0x7E,0x00,0x00 ), # 0x57 87 + 'X': (0x00,0x00,0x33,0x1E,0x0C,0x0C,0x1E,0x33,0x00,0x00 ), # 0x58 88 + 'Y': (0x00,0x00,0x33,0x33,0x33,0x1E,0x0C,0x0C,0x00,0x00 ), # 0x59 89 + 'Z': (0x00,0x00,0x3F,0x38,0x1C,0x0E,0x07,0x3F,0x00,0x00 ), # 0x5A 90 + '[': (0x00,0x0F,0x03,0x03,0x03,0x03,0x03,0x03,0x0F,0x00 ), # 0x5B 91 + '\\': (0x00,0x01,0x03,0x06,0x0C,0x18,0x30,0x60,0x40,0x00 ), # 0x5C 92 + ']': (0x00,0x0F,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0F,0x00 ), # 0x5D 93 + '^': (0x00,0x00,0x0C,0x1E,0x33,0x00,0x00,0x00,0x00,0x00 ), # 0x5E 94 + '_': (0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x00 ), # 0x5F 95 + '`': (0x00,0x00,0x03,0x03,0x02,0x00,0x00,0x00,0x00,0x00 ), # 0x60 96 + 'a': (0x00,0x00,0x00,0x1E,0x30,0x3E,0x33,0x3E,0x00,0x00 ), # 0x61 97 + 'b': (0x00,0x00,0x03,0x1F,0x33,0x33,0x33,0x1F,0x00,0x00 ), # 0x62 98 + 'c': (0x00,0x00,0x00,0x1E,0x33,0x03,0x33,0x1E,0x00,0x00 ), # 0x63 99 + 'd': (0x00,0x00,0x30,0x3E,0x33,0x33,0x33,0x3E,0x00,0x00 ), # 0x64 100 + 'e': (0x00,0x00,0x00,0x1E,0x33,0x1F,0x03,0x1E,0x00,0x00 ), # 0x65 101 + 'f': (0x00,0x00,0x0E,0x03,0x0F,0x03,0x03,0x03,0x00,0x00 ), # 0x66 102 + 'g': (0x00,0x00,0x00,0x3E,0x33,0x33,0x3E,0x30,0x1E,0x00 ), # 0x67 103 + 'h': (0x00,0x00,0x03,0x1F,0x33,0x33,0x33,0x33,0x00,0x00 ), # 0x68 104 + 'i': (0x00,0x00,0x03,0x00,0x03,0x03,0x03,0x03,0x00,0x00 ), # 0x69 105 + 'j': (0x00,0x00,0x06,0x00,0x06,0x06,0x06,0x06,0x03,0x00 ), # 0x6A 106 + 'k': (0x00,0x00,0x03,0x33,0x1B,0x0F,0x1B,0x33,0x00,0x00 ), # 0x6B 107 + 'l': (0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x06,0x00,0x00 ), # 0x6C 108 + 'm': (0x00,0x00,0x00,0x7F,0xDB,0xDB,0xDB,0xDB,0x00,0x00 ), # 0x6D 109 + 'n': (0x00,0x00,0x00,0x1F,0x33,0x33,0x33,0x33,0x00,0x00 ), # 0x6E 110 + 'o': (0x00,0x00,0x00,0x1E,0x33,0x33,0x33,0x1E,0x00,0x00 ), # 0x6F 111 + 'p': (0x00,0x00,0x00,0x1F,0x33,0x33,0x33,0x1F,0x03,0x00 ), # 0x70 112 + 'q': (0x00,0x00,0x00,0x3E,0x33,0x33,0x33,0x3E,0x30,0x00 ), # 0x71 113 + 'r': (0x00,0x00,0x00,0x0E,0x03,0x03,0x03,0x03,0x00,0x00 ), # 0x72 114 + 's': (0x00,0x00,0x00,0x1E,0x03,0x1E,0x30,0x1F,0x00,0x00 ), # 0x73 115 + 't': (0x00,0x00,0x06,0x0F,0x06,0x06,0x06,0x0C,0x00,0x00 ), # 0x74 116 + 'u': (0x00,0x00,0x00,0x33,0x33,0x33,0x33,0x1E,0x00,0x00 ), # 0x75 117 + 'v': (0x00,0x00,0x00,0x33,0x33,0x33,0x1E,0x0C,0x00,0x00 ), # 0x76 118 + 'w': (0x00,0x00,0x00,0xC3,0xDB,0xDB,0xDB,0x7E,0x00,0x00 ), # 0x77 119 + 'x': (0x00,0x00,0x00,0x33,0x1E,0x0C,0x1E,0x33,0x00,0x00 ), # 0x78 120 + 'y': (0x00,0x00,0x00,0x33,0x33,0x33,0x3E,0x30,0x1E,0x00 ), # 0x79 121 + 'z': (0x00,0x00,0x00,0x3F,0x18,0x0C,0x06,0x3F,0x00,0x00 ), # 0x7A 122 + '(': (0x00,0x0C,0x06,0x06,0x03,0x03,0x06,0x06,0x0C,0x00 ), # 0x7B 123 + '|': (0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00 ), # 0x7C 124 + ')': (0x00,0x03,0x06,0x06,0x0C,0x0C,0x06,0x06,0x03,0x00 ), # 0x7D 125 + '~': (0x00,0x00,0x00,0x00,0x6E,0x3B,0x00,0x00,0x00,0x00 ) # 0x7E 126 +} + +def reverse_mask(x): + x = ((x & 0x55) << 1) | ((x & 0xAA) >> 1) + x = ((x & 0x33) << 2) | ((x & 0xCC) >> 2) + x = ((x & 0x0F) << 4) | ((x & 0xF0) >> 4) + return x + +def get_char(c): + try: + char = font_table[c] + except KeyError: + print(f"Unrecognized char: {c}") + char = font_table[" "] + return char + +def plength(para): + # This could be precalculated, but I am lazy + total = 0 + for c in para: + char = get_char(c) + width = sum(1 for i in [sum(1 for i in [x & mask for x in char] if i > 0) for mask in [128, 64, 32, 16, 8, 4, 2, 1]] if i > 0) + total += 5 if width == 0 else width + 1 + return total + +def pprint(display, para, x, y, col): + offset = 0 + display.thickness(1) + + if col == 0: + display.pen(15) + else: + display.pen(0) + + for c in para: + if col == 1: + char = [~x & 0xFF for x in get_char(c)] + else: + char = get_char(c) + + display.image(bytearray([reverse_mask(x) for x in char]), 8, 10, x + offset, y) + display.rectangle(x + offset - 1, y, 1, 10) + + offset += plength(c) + +def ppara(display, para, x, y, width, col): + line = "" + base_x = x + length = 0 + for c in para: + clength = plength(c) + if clength + length > width: + pprint(display, line, x, y, col) + y += 10 + length = 0 + x = base_x + line = "" + + length += clength + line += c + + pprint(display, line, x, y, col) + +def ptitle(display, para, x, y, col): + pprint(display, para, x, y, 0) + + display.thickness(2) + display.pen(0) + display.line(x, y + 10, x + plength(para), y + 10) + +def draw_background(display): + display.pen(0) + display.thickness(1) + + for y in range(11, 63): + for x in range(1, 147, 2): + display.rectangle(x * 2 + (2 if y % 2 == 0 else 0), y * 2, 2, 2) + +# image = bytearray(int(296 * 128 / 8)) +# open("images/{}".format("background.bin"), "r").readinto(image) +# display.image(image, 296, 128, 0, 0) + +def draw_menu(display, selected): + menu = "Badge QR Special About" + + # logo + display.pen(0) + display.thickness(1) + + x = 12 + y = 6 + display.line(x + 2, y, x + 8, y) + display.line(x + 6, y + 1, x + 9, y + 1) + display.line(x + 1, y + 2, x + 10, y + 2) + display.line(x + 5, y + 3, x + 10, y + 3) + display.line(x, y + 4, x + 10, y + 4) + display.line(x + 5, y + 5, x + 10, y + 5) + display.line(x + 1, y + 6, x + 10, y + 6) + display.line(x + 6, y + 7, x + 9, y + 7) + display.line(x + 2, y + 8, x + 8, y + 8) + + x = 40 + pprint(display, menu, x, 6, 0) + + # selected + display.pen(0) + offset = plength(menu.split(selected)[0]) + display.rectangle(x + offset - 7, 3, plength(selected) + 13, 17) + pprint(display, selected, x + offset, 6, 1) + + display.pen(0) + display.thickness(2) + display.line(1, 21, 295, 21) + +def draw_border(display): + display.pen(0) + display.thickness(2) + + display.line(1, 1, 295, 1) + display.line(1, 1, 1, 127) + display.line(1, 127, 295, 127) + display.line(295, 1, 295, 127) + display.image(bytearray((0xff, 0xff, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0xc0)), 8, 8, 0, 0) + display.image(bytearray((0xff, 0xff, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x03)), 8, 8, 288, 0) + display.image(bytearray((0x03, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0xff, 0xff)), 8, 8, 288, 120) + display.image(bytearray((0xc0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xff, 0xff)), 8, 8, 0, 120) + +def map_value(input, in_min, in_max, out_min, out_max): + return (((input - in_min) * (out_max - out_min)) / (in_max - in_min)) + out_min + +def draw_battery(display, x, y): + vbat = badger_os.get_battery_level() + level = int(map_value(vbat, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE, 0, 4)) + + # Outline + display.thickness(1) + display.pen(15) + display.rectangle(x, y, 19, 10) + + # Terminal + display.rectangle(x + 19, y + 3, 2, 4) + display.pen(0) + display.rectangle(x + 1, y + 1, 17, 8) + if level < 1: + display.pen(0) + display.line(x + 3, y, x + 3 + 10, y + 10) + display.line(x + 3 + 1, y, x + 3 + 11, y + 10) + display.pen(15) + display.line(x + 2 + 2, y - 1, x + 4 + 12, y + 11) + display.line(x + 2 + 3, y - 1, x + 4 + 13, y + 11) + return + + # Battery Bars + display.pen(15) + for i in range(4): + if level / 4 > (1.0 * i) / 4: + display.rectangle(i * 4 + x + 2, y + 2, 3, 6) + +def draw_window(display, x, y, width, height, title): + display.thickness(1) + + # borders + display.pen(15) + display.rectangle(x, y, width, height) + + display.pen(0) + display.line(x, y, x + width - 1, y) + display.line(x, y, x, y + height - 1) + display.line(x, y + height - 1, x + width - 1, y + height - 1) + display.line(x + width - 1, y, x + width - 1, y + height - 1) + + # shadow + display.line(x, y + height, x + width + 1, y + height) + display.line(x + width, y, x + width, y + height + 1) + display.line(x + 1, y + height + 1, x + width + 1, y + height + 1) + display.line(x + width + 1, y + 1, x + width + 1, y + height + 1) + + # title + display.line(x + 4, y + 3, x + width - 4, y + 3) + display.line(x + 4, y + 5, x + width - 4, y + 5) + display.line(x + 4, y + 7, x + width - 4, y + 7) + + display.line(x, y + 11, x + width, y + 11) + display.line(x, y + 11, x + width, y + 11) + + pprint(display, title, (x + x + width - plength(title)) // 2, y + 1, 0) + +def wait_for_user_to_release_buttons(display): + pr = display.pressed + while pr(badger2040.BUTTON_A) or pr(badger2040.BUTTON_B) or pr(badger2040.BUTTON_C) or pr(badger2040.BUTTON_UP) or pr(badger2040.BUTTON_DOWN): + time.sleep(0.01) + +def launch_app(display, file): + wait_for_user_to_release_buttons(display) + + for k in locals().keys(): + if k not in ("gc", "file", "badger_os"): + del locals()[k] + gc.collect() + badger_os.launch(file) + +def button(display, pin): + if not display.pressed(badger2040.BUTTON_USER): # User button is NOT held down + if pin == badger2040.BUTTON_A: + launch_app(display, "_badge_app") + if pin == badger2040.BUTTON_B: + launch_app(display, "_qr_app") + if pin == badger2040.BUTTON_C: + launch_app(display, "_fortune_app") + +def draw_ui(display, selected): + draw_border(display) + draw_menu(display, selected) + draw_background(display) + draw_battery(display, WIDTH - 28, 6) \ No newline at end of file