Badger 2040, tests Ferreol et Capucine
BIN
badger2040/badge-image.bin
Normal file
6
badger2040/badge.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Fringe Division
|
||||||
|
Diane Ducastel
|
||||||
|
role
|
||||||
|
Special agent
|
||||||
|
universe
|
||||||
|
Blue
|
BIN
badger2040/capucine-badge.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
badger2040/diane-badge-image.bin
Normal file
BIN
badger2040/diane-badge-image.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
badger2040/ferreol-badge-2.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
badger2040/ferreol-badge-image.bin
Normal file
BIN
badger2040/ferreol-badge.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
badger2040/images/QR-code-buzz-1.bin
Normal file
BIN
badger2040/images/QR-code-buzz-2.bin
Normal file
BIN
badger2040/images/badgerpunk.bin
Normal file
8
badger2040/images/readme.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
Images must be 296x128 pixel with 1bit colour depth.
|
||||||
|
|
||||||
|
You can use examples/badger2040/image_converter/convert.py to convert them:
|
||||||
|
|
||||||
|
python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png
|
||||||
|
|
||||||
|
Create a new "images" directory via Thonny, and upload the .bin files there.
|
1
badger2040/launcher.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"font_size": 1, "running": "badge", "page": 1, "inverted": false}
|
136
badger2040/libs/image_converter/convert.py
Executable file
@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Converts images into a format suitable for display on Badger 2040.
|
||||||
|
|
||||||
|
Optionally resizes images to 296x128 to fit the display.
|
||||||
|
|
||||||
|
Crunches images down to dithered, 1bit colour depth.
|
||||||
|
|
||||||
|
Outputs either in raw binary format or as a .py file for embedding into MicroPython.
|
||||||
|
|
||||||
|
Output to py functionality is borrwed from data_to_py.py, Copyright (c) 2016 Peter Hinch
|
||||||
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
|
import argparse
|
||||||
|
from PIL import Image, ImageEnhance
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PY_HEADER = """# Code generated by convert.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
PY_FOOTER = """_mvdata = memoryview(_data)
|
||||||
|
|
||||||
|
def data():
|
||||||
|
return _mvdata
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Converts images into the format used by Badger2040.')
|
||||||
|
parser.add_argument('file', nargs="+", help='input files to convert')
|
||||||
|
parser.add_argument('--out_dir', type=Path, default=None, help='output directory')
|
||||||
|
parser.add_argument('--binary', action="store_true", help='output binary file for MicroPython')
|
||||||
|
parser.add_argument('--py', action="store_true", help='output .py file for MicroPython embedding')
|
||||||
|
parser.add_argument('--resize', action="store_true", help='force images to 296x128 pixels')
|
||||||
|
|
||||||
|
options = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
class ByteWriter(object):
|
||||||
|
bytes_per_line = 16
|
||||||
|
|
||||||
|
def __init__(self, stream, varname):
|
||||||
|
self.stream = stream
|
||||||
|
self.stream.write('{} =\\\n'.format(varname))
|
||||||
|
self.bytecount = 0 # For line breaks
|
||||||
|
|
||||||
|
def _eol(self):
|
||||||
|
self.stream.write("'\\\n")
|
||||||
|
|
||||||
|
def _eot(self):
|
||||||
|
self.stream.write("'\n")
|
||||||
|
|
||||||
|
def _bol(self):
|
||||||
|
self.stream.write("b'")
|
||||||
|
|
||||||
|
# Output a single byte
|
||||||
|
def obyte(self, data):
|
||||||
|
if not self.bytecount:
|
||||||
|
self._bol()
|
||||||
|
self.stream.write('\\x{:02x}'.format(data))
|
||||||
|
self.bytecount += 1
|
||||||
|
self.bytecount %= self.bytes_per_line
|
||||||
|
if not self.bytecount:
|
||||||
|
self._eol()
|
||||||
|
|
||||||
|
# Output from a sequence
|
||||||
|
def odata(self, bytelist):
|
||||||
|
for byt in bytelist:
|
||||||
|
self.obyte(byt)
|
||||||
|
|
||||||
|
# ensure a correct final line
|
||||||
|
def eot(self): # User force EOL if one hasn't occurred
|
||||||
|
if self.bytecount:
|
||||||
|
self._eot()
|
||||||
|
self.stream.write('\n')
|
||||||
|
|
||||||
|
|
||||||
|
def convert_image(img):
|
||||||
|
if options.resize:
|
||||||
|
img = img.resize((296, 128)) # resize
|
||||||
|
try:
|
||||||
|
enhancer = ImageEnhance.Contrast(img)
|
||||||
|
img = enhancer.enhance(2.0)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
img = img.convert("1") # convert to black and white
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def write_stream(header, footer, ip_stream, op_stream):
|
||||||
|
op_stream.write(header)
|
||||||
|
op_stream.write('\n')
|
||||||
|
data = ip_stream.read()
|
||||||
|
bw_data = ByteWriter(op_stream, '_data')
|
||||||
|
bw_data.odata(data)
|
||||||
|
bw_data.eot()
|
||||||
|
op_stream.write(footer)
|
||||||
|
|
||||||
|
|
||||||
|
# create map of images based on input filenames
|
||||||
|
for input_filename in options.file:
|
||||||
|
with Image.open(input_filename) as img:
|
||||||
|
img = convert_image(img)
|
||||||
|
|
||||||
|
image_name = Path(input_filename).stem
|
||||||
|
|
||||||
|
w, h = img.size
|
||||||
|
|
||||||
|
output_data = [~b & 0xff for b in list(img.tobytes())]
|
||||||
|
|
||||||
|
if options.binary:
|
||||||
|
if options.out_dir is not None:
|
||||||
|
output_filename = (options.out_dir / image_name).with_suffix(".bin")
|
||||||
|
else:
|
||||||
|
output_filename = Path(input_filename).with_suffix(".bin")
|
||||||
|
print(f"Saving to {output_filename}, {w}x{h}")
|
||||||
|
with open(output_filename, "wb") as out:
|
||||||
|
out.write(bytearray(output_data))
|
||||||
|
elif options.py:
|
||||||
|
if options.out_dir is not None:
|
||||||
|
output_filename = (options.out_dir / image_name).with_suffix(".py")
|
||||||
|
else:
|
||||||
|
output_filename = Path(input_filename).with_suffix(".py")
|
||||||
|
print(f"Saving to {output_filename}, {w}x{h}")
|
||||||
|
with open(output_filename, "w") as out:
|
||||||
|
write_stream(PY_HEADER, PY_FOOTER, io.BytesIO(bytes(output_data)), out)
|
||||||
|
else:
|
||||||
|
image_code = '''\
|
||||||
|
static const uint8_t {image_name}[{count}] = {{
|
||||||
|
{byte_data}
|
||||||
|
}};
|
||||||
|
'''.format(image_name=image_name, count=len(output_data), byte_data=", ".join(str(b) for b in output_data))
|
||||||
|
|
||||||
|
print(image_code)
|
154
badger2040/libs/image_converter/data_to_py.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#! /usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Peter Hinch
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# UTILITIES FOR WRITING PYTHON SOURCECODE TO A FILE
|
||||||
|
|
||||||
|
# ByteWriter takes as input a variable name and data values and writes
|
||||||
|
# Python source to an output stream of the form
|
||||||
|
# my_variable = b'\x01\x02\x03\x04\x05\x06\x07\x08'\
|
||||||
|
|
||||||
|
# Lines are broken with \ for readability.
|
||||||
|
|
||||||
|
|
||||||
|
class ByteWriter(object):
|
||||||
|
bytes_per_line = 16
|
||||||
|
|
||||||
|
def __init__(self, stream, varname):
|
||||||
|
self.stream = stream
|
||||||
|
self.stream.write('{} =\\\n'.format(varname))
|
||||||
|
self.bytecount = 0 # For line breaks
|
||||||
|
|
||||||
|
def _eol(self):
|
||||||
|
self.stream.write("'\\\n")
|
||||||
|
|
||||||
|
def _eot(self):
|
||||||
|
self.stream.write("'\n")
|
||||||
|
|
||||||
|
def _bol(self):
|
||||||
|
self.stream.write("b'")
|
||||||
|
|
||||||
|
# Output a single byte
|
||||||
|
def obyte(self, data):
|
||||||
|
if not self.bytecount:
|
||||||
|
self._bol()
|
||||||
|
self.stream.write('\\x{:02x}'.format(data))
|
||||||
|
self.bytecount += 1
|
||||||
|
self.bytecount %= self.bytes_per_line
|
||||||
|
if not self.bytecount:
|
||||||
|
self._eol()
|
||||||
|
|
||||||
|
# Output from a sequence
|
||||||
|
def odata(self, bytelist):
|
||||||
|
for byt in bytelist:
|
||||||
|
self.obyte(byt)
|
||||||
|
|
||||||
|
# ensure a correct final line
|
||||||
|
def eot(self): # User force EOL if one hasn't occurred
|
||||||
|
if self.bytecount:
|
||||||
|
self._eot()
|
||||||
|
self.stream.write('\n')
|
||||||
|
|
||||||
|
|
||||||
|
# PYTHON FILE WRITING
|
||||||
|
|
||||||
|
STR01 = """# Code generated by data_to_py.py.
|
||||||
|
version = '0.1'
|
||||||
|
"""
|
||||||
|
|
||||||
|
STR02 = """_mvdata = memoryview(_data)
|
||||||
|
|
||||||
|
def data():
|
||||||
|
return _mvdata
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def write_func(stream, name, arg):
|
||||||
|
stream.write('def {}():\n return {}\n\n'.format(name, arg))
|
||||||
|
|
||||||
|
|
||||||
|
def write_data(op_path, ip_path):
|
||||||
|
try:
|
||||||
|
with open(ip_path, 'rb') as ip_stream:
|
||||||
|
try:
|
||||||
|
with open(op_path, 'w') as op_stream:
|
||||||
|
write_stream(ip_stream, op_stream)
|
||||||
|
except OSError:
|
||||||
|
print("Can't open", op_path, 'for writing')
|
||||||
|
return False
|
||||||
|
except OSError:
|
||||||
|
print("Can't open", ip_path)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def write_stream(ip_stream, op_stream):
|
||||||
|
op_stream.write(STR01)
|
||||||
|
op_stream.write('\n')
|
||||||
|
data = ip_stream.read()
|
||||||
|
bw_data = ByteWriter(op_stream, '_data')
|
||||||
|
bw_data.odata(data)
|
||||||
|
bw_data.eot()
|
||||||
|
op_stream.write(STR02)
|
||||||
|
|
||||||
|
|
||||||
|
# PARSE COMMAND LINE ARGUMENTS
|
||||||
|
|
||||||
|
def quit(msg):
|
||||||
|
print(msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
DESC = """data_to_py.py
|
||||||
|
Utility to convert an arbitrary binary file to Python source.
|
||||||
|
Sample usage:
|
||||||
|
data_to_py.py image.jpg image.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(__file__, description=DESC,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument('infile', type=str, help='Input file path')
|
||||||
|
parser.add_argument('outfile', type=str,
|
||||||
|
help='Path and name of output file. Must have .py extension.')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not os.path.isfile(args.infile):
|
||||||
|
quit("Data filename does not exist")
|
||||||
|
|
||||||
|
if not os.path.splitext(args.outfile)[1].upper() == '.PY':
|
||||||
|
quit('Output filename must have a .py extension.')
|
||||||
|
|
||||||
|
print('Writing Python file.')
|
||||||
|
if not write_data(args.outfile, args.infile):
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(args.outfile, 'written successfully.')
|
BIN
badger2040/libs/image_converter/test-images/adam.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
badger2040/libs/image_converter/test-images/paul.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
badger2040/libs/image_converter/test-images/shaun.png
Normal file
After Width: | Height: | Size: 46 KiB |
92
badger2040/os/badger2040/README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Badger 2040 Examples <!-- omit in toc -->
|
||||||
|
|
||||||
|
- [Function Examples](#function-examples)
|
||||||
|
- [Battery](#battery)
|
||||||
|
- [Button Test](#button-test)
|
||||||
|
- [LED](#led)
|
||||||
|
- [Pin interrupt](#pin-interrupt)
|
||||||
|
- [Application Examples](#application-examples)
|
||||||
|
- [Badge](#badge)
|
||||||
|
- [Checklist](#checklist)
|
||||||
|
- [Clock](#clock)
|
||||||
|
- [E-Book](#e-book)
|
||||||
|
- [Fonts](#fonts)
|
||||||
|
- [Image](#image)
|
||||||
|
- [QR gen](#qr-gen)
|
||||||
|
- [Launcher](#launcher)
|
||||||
|
|
||||||
|
|
||||||
|
## Function Examples
|
||||||
|
|
||||||
|
### Battery
|
||||||
|
[battery.py](battery.py)
|
||||||
|
|
||||||
|
An example of how to read the battery voltage and display a battery level indicator.
|
||||||
|
|
||||||
|
### Button Test
|
||||||
|
[button_test.py](button_test.py)
|
||||||
|
|
||||||
|
An example of how to read Badger2040's buttons and display a unique message for each.
|
||||||
|
|
||||||
|
### LED
|
||||||
|
[led.py](led.py)
|
||||||
|
|
||||||
|
Blinks Badger's LED on and off.
|
||||||
|
|
||||||
|
### Pin interrupt
|
||||||
|
[pin_interrupt.py](pin_interrupt.py)
|
||||||
|
|
||||||
|
An example of drawing text and graphics and using the buttons.
|
||||||
|
|
||||||
|
## Application Examples
|
||||||
|
|
||||||
|
### Badge
|
||||||
|
[badge.py](badge.py)
|
||||||
|
|
||||||
|
Create your own name badge! This application looks for two files on your MicroPython drive:
|
||||||
|
* `badge.txt` - A text file containing 6 lines, corresponding to the 6 different pieces of text on the badge
|
||||||
|
* `badge-image.bin` - A 108x128px 1-bit colour depth image to display alongside the text. You can use `examples/badger2040/image_converter/convert.py` to convert them:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
[list.py](list.py)
|
||||||
|
|
||||||
|
A checklist application, letting you navigate through items and tick each of them off.
|
||||||
|
|
||||||
|
### Clock
|
||||||
|
[clock.py](clock.py)
|
||||||
|
|
||||||
|
A simple clock showing the time and date, that uses the E Ink's fast speed to update every second
|
||||||
|
|
||||||
|
### E-Book
|
||||||
|
[ebook.py](ebook.py)
|
||||||
|
|
||||||
|
A mini text file e-reader. Comes pre-loaded with an excerpt of The Wind In the Willows.
|
||||||
|
|
||||||
|
### Fonts
|
||||||
|
[fonts.py](fonts.py)
|
||||||
|
|
||||||
|
A demonstration of the various fonts that can be used in your programs.
|
||||||
|
|
||||||
|
### Image
|
||||||
|
[image.py](image.py)
|
||||||
|
|
||||||
|
An image gallery. Displays and lets you cycle through any images stored within the MicroPython device's `/images` directory. Images must be 296x128 pixels with 1-bit colour depth. You can use `examples/badger2040/image_converter/convert.py` to convert them:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### QR gen
|
||||||
|
[qrgen.py](qrgen.py)
|
||||||
|
|
||||||
|
This application looks for a file on your MicroPython drive:
|
||||||
|
- `qrcode.txt` - A text file containing 9 lines. The first line should be a URL which will be converted into and displayed as a QR code. Up to 8 more lines of information can be added, which will be shown as plain text to the right of the QR code.
|
||||||
|
|
||||||
|
### Launcher
|
||||||
|
[launcher.py](launcher.py)
|
||||||
|
|
||||||
|
A launcher-style application, that provide a menu of other applications that can be loaded, as well as information such as battery level.
|
BIN
badger2040/os/badger2040/assets/badge_image.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
badger2040/os/badger2040/assets/badgerpunk.png
Normal file
After Width: | Height: | Size: 36 KiB |
6
badger2040/os/badger2040/assets/boot.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
try:
|
||||||
|
open("main.py", "r")
|
||||||
|
except OSError:
|
||||||
|
with open("main.py", "w") as f:
|
||||||
|
f.write("import _launcher")
|
||||||
|
f.flush()
|
BIN
badger2040/os/badger2040/assets/launchericons.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
267
badger2040/os/badger2040/badge.py
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import badger2040
|
||||||
|
import machine
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Global Constants
|
||||||
|
WIDTH = badger2040.WIDTH
|
||||||
|
HEIGHT = badger2040.HEIGHT
|
||||||
|
|
||||||
|
IMAGE_WIDTH = 104
|
||||||
|
|
||||||
|
COMPANY_HEIGHT = 30
|
||||||
|
DETAILS_HEIGHT = 20
|
||||||
|
NAME_HEIGHT = HEIGHT - COMPANY_HEIGHT - (DETAILS_HEIGHT * 2) - 2
|
||||||
|
TEXT_WIDTH = WIDTH - IMAGE_WIDTH - 1
|
||||||
|
|
||||||
|
COMPANY_TEXT_SIZE = 0.6
|
||||||
|
DETAILS_TEXT_SIZE = 0.5
|
||||||
|
|
||||||
|
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
|
||||||
|
2MB Flash
|
||||||
|
E ink
|
||||||
|
296x128px"""
|
||||||
|
|
||||||
|
BADGE_IMAGE = bytearray(int(IMAGE_WIDTH * HEIGHT / 8))
|
||||||
|
|
||||||
|
try:
|
||||||
|
open("badge-image.bin", "rb").readinto(BADGE_IMAGE)
|
||||||
|
except OSError:
|
||||||
|
try:
|
||||||
|
import badge_image
|
||||||
|
BADGE_IMAGE = bytearray(badge_image.data())
|
||||||
|
del badge_image
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Utility functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Reduce the size of a string until it fits within a given width
|
||||||
|
def truncatestring(text, text_size, width):
|
||||||
|
while True:
|
||||||
|
length = display.measure_text(text, text_size)
|
||||||
|
if length > 0 and length > width:
|
||||||
|
text = text[:-1]
|
||||||
|
else:
|
||||||
|
text += ""
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# 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)
|
||||||
|
display.clear()
|
||||||
|
|
||||||
|
# Draw badge image
|
||||||
|
display.image(BADGE_IMAGE, IMAGE_WIDTH, HEIGHT, WIDTH - IMAGE_WIDTH, 0)
|
||||||
|
|
||||||
|
# Draw a border around the image
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(1)
|
||||||
|
display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - 1, 0)
|
||||||
|
display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - IMAGE_WIDTH, HEIGHT - 1)
|
||||||
|
display.line(WIDTH - IMAGE_WIDTH, HEIGHT - 1, WIDTH - 1, HEIGHT - 1)
|
||||||
|
display.line(WIDTH - 1, 0, WIDTH - 1, HEIGHT - 1)
|
||||||
|
|
||||||
|
# Uncomment this if a white background is wanted behind the company
|
||||||
|
# display.pen(15)
|
||||||
|
# display.rectangle(1, 1, TEXT_WIDTH, COMPANY_HEIGHT - 1)
|
||||||
|
|
||||||
|
# Draw the company
|
||||||
|
display.pen(15) # Change this to 0 if a white background is used
|
||||||
|
display.font("serif")
|
||||||
|
display.thickness(3)
|
||||||
|
display.text(company, LEFT_PADDING, (COMPANY_HEIGHT // 2) + 1, COMPANY_TEXT_SIZE)
|
||||||
|
|
||||||
|
# Draw a white background behind the name
|
||||||
|
display.pen(15)
|
||||||
|
display.thickness(1)
|
||||||
|
display.rectangle(1, COMPANY_HEIGHT + 1, TEXT_WIDTH, NAME_HEIGHT)
|
||||||
|
|
||||||
|
# Draw the name, scaling it based on the available width
|
||||||
|
display.pen(0)
|
||||||
|
display.font("sans")
|
||||||
|
display.thickness(4)
|
||||||
|
name_size = 2.0 # A sensible starting scale
|
||||||
|
while True:
|
||||||
|
name_length = display.measure_text(name, name_size)
|
||||||
|
if name_length >= (TEXT_WIDTH - NAME_PADDING) and name_size >= 0.1:
|
||||||
|
name_size -= 0.01
|
||||||
|
else:
|
||||||
|
display.text(name, (TEXT_WIDTH - name_length) // 2, (NAME_HEIGHT // 2) + COMPANY_HEIGHT + 1, name_size)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Draw a white backgrounds behind the details
|
||||||
|
display.pen(15)
|
||||||
|
display.thickness(1)
|
||||||
|
display.rectangle(1, HEIGHT - DETAILS_HEIGHT * 2, TEXT_WIDTH, DETAILS_HEIGHT - 1)
|
||||||
|
display.rectangle(1, HEIGHT - DETAILS_HEIGHT, TEXT_WIDTH, DETAILS_HEIGHT - 1)
|
||||||
|
|
||||||
|
# Draw the first detail's title and text
|
||||||
|
display.pen(0)
|
||||||
|
display.font("sans")
|
||||||
|
display.thickness(3)
|
||||||
|
name_length = display.measure_text(detail1_title, DETAILS_TEXT_SIZE)
|
||||||
|
display.text(detail1_title, LEFT_PADDING, HEIGHT - ((DETAILS_HEIGHT * 3) // 2), DETAILS_TEXT_SIZE)
|
||||||
|
display.thickness(2)
|
||||||
|
display.text(detail1_text, 5 + name_length + DETAIL_SPACING, HEIGHT - ((DETAILS_HEIGHT * 3) // 2), DETAILS_TEXT_SIZE)
|
||||||
|
|
||||||
|
# Draw the second detail's title and text
|
||||||
|
display.thickness(3)
|
||||||
|
name_length = display.measure_text(detail2_title, DETAILS_TEXT_SIZE)
|
||||||
|
display.text(detail2_title, LEFT_PADDING, HEIGHT - (DETAILS_HEIGHT // 2), DETAILS_TEXT_SIZE)
|
||||||
|
display.thickness(2)
|
||||||
|
display.text(detail2_text, LEFT_PADDING + name_length + DETAIL_SPACING, HEIGHT - (DETAILS_HEIGHT // 2), DETAILS_TEXT_SIZE)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Program setup
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
show_overlay = False
|
||||||
|
|
||||||
|
# Create a new Badger and set it to update NORMAL
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.update_speed(badger2040.UPDATE_NORMAL)
|
||||||
|
|
||||||
|
# Open the badge file
|
||||||
|
try:
|
||||||
|
badge = open("badge.txt", "r")
|
||||||
|
except OSError:
|
||||||
|
badge = open("badge.txt", "w")
|
||||||
|
badge.write(DEFAULT_TEXT)
|
||||||
|
badge.flush()
|
||||||
|
badge.seek(0)
|
||||||
|
|
||||||
|
# Read in the next 6 lines
|
||||||
|
company = badge.readline() # "mustelid inc"
|
||||||
|
name = badge.readline() # "H. Badger"
|
||||||
|
detail1_title = badge.readline() # "RP2040"
|
||||||
|
detail1_text = badge.readline() # "2MB Flash"
|
||||||
|
detail2_title = badge.readline() # "E ink"
|
||||||
|
detail2_text = badge.readline() # "296x128px"
|
||||||
|
|
||||||
|
# Truncate all of the text (except for the name as that is scaled)
|
||||||
|
company = truncatestring(company, COMPANY_TEXT_SIZE, TEXT_WIDTH)
|
||||||
|
|
||||||
|
detail1_title = truncatestring(detail1_title, DETAILS_TEXT_SIZE, TEXT_WIDTH)
|
||||||
|
detail1_text = truncatestring(detail1_text, DETAILS_TEXT_SIZE,
|
||||||
|
TEXT_WIDTH - DETAIL_SPACING - display.measure_text(detail1_title, DETAILS_TEXT_SIZE))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
# Set up the buttons
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
|
||||||
|
# Button handling function
|
||||||
|
def button(pin):
|
||||||
|
global show_overlay
|
||||||
|
|
||||||
|
if pin == button_a:
|
||||||
|
show_overlay = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if pin == button_b:
|
||||||
|
show_overlay = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if pin == button_c:
|
||||||
|
show_overlay = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if pin == button_up:
|
||||||
|
show_overlay = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if pin == button_down:
|
||||||
|
show_overlay = True
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# Register the button handling function with the buttons
|
||||||
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Main program loop
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
draw_badge()
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if show_overlay:
|
||||||
|
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()
|
||||||
|
time.sleep(4)
|
||||||
|
|
||||||
|
draw_badge()
|
||||||
|
display.update()
|
||||||
|
show_overlay = False
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
163
badger2040/os/badger2040/battery.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import badger2040
|
||||||
|
from machine import Pin, ADC
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Global Constants
|
||||||
|
# for e.g. 2xAAA batteries, try max 3.4 min 3.0
|
||||||
|
MAX_BATTERY_VOLTAGE = 4.0
|
||||||
|
MIN_BATTERY_VOLTAGE = 3.2
|
||||||
|
|
||||||
|
WIDTH = badger2040.WIDTH
|
||||||
|
HEIGHT = badger2040.HEIGHT
|
||||||
|
|
||||||
|
BATT_WIDTH = 200
|
||||||
|
BATT_HEIGHT = 100
|
||||||
|
BATT_BORDER = 10
|
||||||
|
BATT_TERM_WIDTH = 20
|
||||||
|
BATT_TERM_HEIGHT = 50
|
||||||
|
BATT_BAR_PADDING = 10
|
||||||
|
BATT_BAR_HEIGHT = BATT_HEIGHT - (BATT_BORDER * 2) - (BATT_BAR_PADDING * 2)
|
||||||
|
BATT_BAR_START = ((WIDTH - BATT_WIDTH) // 2) + BATT_BORDER + BATT_BAR_PADDING
|
||||||
|
BATT_BAR_END = ((WIDTH + BATT_WIDTH) // 2) - BATT_BORDER - BATT_BAR_PADDING
|
||||||
|
|
||||||
|
NUM_BATT_BARS = 4
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Utility functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Drawing functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Draw the frame of the reader
|
||||||
|
def draw_battery(level, resolution):
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
|
||||||
|
display.thickness(1)
|
||||||
|
|
||||||
|
# Draw the battery outline
|
||||||
|
display.pen(0)
|
||||||
|
display.rectangle(
|
||||||
|
(WIDTH - BATT_WIDTH) // 2, (HEIGHT - BATT_HEIGHT) // 2, BATT_WIDTH, BATT_HEIGHT
|
||||||
|
)
|
||||||
|
|
||||||
|
display.rectangle(
|
||||||
|
(WIDTH + BATT_WIDTH) // 2,
|
||||||
|
(HEIGHT - BATT_TERM_HEIGHT) // 2,
|
||||||
|
BATT_TERM_WIDTH,
|
||||||
|
BATT_TERM_HEIGHT,
|
||||||
|
)
|
||||||
|
|
||||||
|
display.pen(15)
|
||||||
|
display.rectangle(
|
||||||
|
(WIDTH - BATT_WIDTH) // 2 + BATT_BORDER,
|
||||||
|
(HEIGHT - BATT_HEIGHT) // 2 + BATT_BORDER,
|
||||||
|
BATT_WIDTH - BATT_BORDER * 2,
|
||||||
|
BATT_HEIGHT - BATT_BORDER * 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add a special check for no battery
|
||||||
|
if level < 1:
|
||||||
|
X = WIDTH // 2
|
||||||
|
Y = HEIGHT // 2
|
||||||
|
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(1)
|
||||||
|
thickness = (BATT_BORDER * 3) // 2
|
||||||
|
start_extra = thickness // 3
|
||||||
|
end_extra = (thickness * 2) // 3
|
||||||
|
for i in range(0, thickness):
|
||||||
|
excess = i // 2
|
||||||
|
display.line(
|
||||||
|
X - (BATT_HEIGHT // 2) + i - excess - start_extra,
|
||||||
|
Y - (BATT_HEIGHT // 2) - excess - start_extra,
|
||||||
|
X + (BATT_HEIGHT // 2) + i - excess + end_extra,
|
||||||
|
Y + (BATT_HEIGHT // 2) - excess + end_extra,
|
||||||
|
)
|
||||||
|
display.pen(15)
|
||||||
|
for i in range(0 - thickness, 0):
|
||||||
|
display.line(
|
||||||
|
X - (BATT_HEIGHT // 2) + i,
|
||||||
|
Y - (BATT_HEIGHT // 2),
|
||||||
|
X + (BATT_HEIGHT // 2) + i,
|
||||||
|
Y + (BATT_HEIGHT // 2),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Draw the battery bars
|
||||||
|
display.pen(0)
|
||||||
|
length = (
|
||||||
|
BATT_BAR_END - BATT_BAR_START - ((NUM_BATT_BARS - 1) * BATT_BAR_PADDING)
|
||||||
|
) // NUM_BATT_BARS
|
||||||
|
current_level = 0.0
|
||||||
|
normalised_level = level / resolution
|
||||||
|
for i in range(NUM_BATT_BARS):
|
||||||
|
current_level = (1.0 * i) / NUM_BATT_BARS
|
||||||
|
if normalised_level > current_level:
|
||||||
|
pos = i * (length + BATT_BAR_PADDING)
|
||||||
|
display.rectangle(
|
||||||
|
BATT_BAR_START + pos,
|
||||||
|
(HEIGHT - BATT_BAR_HEIGHT) // 2,
|
||||||
|
length,
|
||||||
|
BATT_BAR_HEIGHT,
|
||||||
|
)
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Program setup
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Create a new Badger and set it to update FAST
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.update_speed(badger2040.UPDATE_FAST)
|
||||||
|
|
||||||
|
# Set up the ADCs for measuring battery voltage
|
||||||
|
vbat_adc = ADC(badger2040.PIN_BATTERY)
|
||||||
|
vref_adc = ADC(badger2040.PIN_1V2_REF)
|
||||||
|
vref_en = Pin(badger2040.PIN_VREF_POWER)
|
||||||
|
vref_en.init(Pin.OUT)
|
||||||
|
vref_en.value(0)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Main program loop
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
last_level = -1
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Print out the voltage
|
||||||
|
print("Battery Voltage = ", vbat, "V", sep="")
|
||||||
|
|
||||||
|
# Convert the voltage to a level to display onscreen
|
||||||
|
level = int(
|
||||||
|
map_value(vbat, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE, 0, NUM_BATT_BARS)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only draw if the battery level has changed significantly
|
||||||
|
if level != last_level:
|
||||||
|
draw_battery(level, NUM_BATT_BARS)
|
||||||
|
last_level = level
|
||||||
|
|
||||||
|
time.sleep(1)
|
67
badger2040/os/badger2040/button_test.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import badger2040
|
||||||
|
import machine
|
||||||
|
import time
|
||||||
|
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.update_speed(badger2040.UPDATE_TURBO)
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
# the User button (boot/usr on back of board) is inverted from the others
|
||||||
|
button_user = machine.Pin(badger2040.BUTTON_USER, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||||
|
|
||||||
|
|
||||||
|
message = None
|
||||||
|
message_y = 60
|
||||||
|
|
||||||
|
|
||||||
|
def button(pin):
|
||||||
|
global message
|
||||||
|
if message is not None:
|
||||||
|
return
|
||||||
|
if pin == button_a:
|
||||||
|
message = "Button a"
|
||||||
|
return
|
||||||
|
if pin == button_b:
|
||||||
|
message = "Button b"
|
||||||
|
return
|
||||||
|
if pin == button_c:
|
||||||
|
message = "Button c"
|
||||||
|
return
|
||||||
|
if pin == button_up:
|
||||||
|
message = "Button Up"
|
||||||
|
return
|
||||||
|
if pin == button_down:
|
||||||
|
message = "Button Down"
|
||||||
|
return
|
||||||
|
if pin == button_user:
|
||||||
|
message = "Button Usr"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_user.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if message is not None:
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(4)
|
||||||
|
display.text(message, 6, message_y, 1.4)
|
||||||
|
for _ in range(2):
|
||||||
|
display.update()
|
||||||
|
message = None
|
||||||
|
time.sleep(0.1)
|
148
badger2040/os/badger2040/clock.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import time
|
||||||
|
import machine
|
||||||
|
import badger2040
|
||||||
|
|
||||||
|
|
||||||
|
rtc = machine.RTC()
|
||||||
|
screen = badger2040.Badger2040()
|
||||||
|
screen.update_speed(badger2040.UPDATE_TURBO)
|
||||||
|
screen.font("gothic")
|
||||||
|
|
||||||
|
cursors = ["year", "month", "day", "hour", "minute"]
|
||||||
|
set_clock = False
|
||||||
|
cursor = 0
|
||||||
|
last = 0
|
||||||
|
|
||||||
|
# Set up the buttons
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
|
||||||
|
def days_in_month(month, year):
|
||||||
|
if month == 2 and ((year % 4 == 0 and year % 100 != 0) or year % 400 == 0):
|
||||||
|
return 29
|
||||||
|
return (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month - 1]
|
||||||
|
|
||||||
|
|
||||||
|
# Button handling function
|
||||||
|
def button(pin):
|
||||||
|
global last, set_clock, cursor, year, month, day, hour, minute
|
||||||
|
|
||||||
|
time.sleep(0.05)
|
||||||
|
if not pin.value():
|
||||||
|
return
|
||||||
|
|
||||||
|
adjust = 0
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
if pin == button_b:
|
||||||
|
set_clock = not set_clock
|
||||||
|
changed = True
|
||||||
|
if not set_clock:
|
||||||
|
rtc.datetime((year, month, day, 0, hour, minute, second, 0))
|
||||||
|
|
||||||
|
if set_clock:
|
||||||
|
if pin == button_c:
|
||||||
|
cursor += 1
|
||||||
|
cursor %= len(cursors)
|
||||||
|
|
||||||
|
if pin == button_a:
|
||||||
|
cursor -= 1
|
||||||
|
cursor %= len(cursors)
|
||||||
|
|
||||||
|
if pin == button_up:
|
||||||
|
adjust = 1
|
||||||
|
|
||||||
|
if pin == button_down:
|
||||||
|
adjust = -1
|
||||||
|
|
||||||
|
if cursors[cursor] == "year":
|
||||||
|
year += adjust
|
||||||
|
year = max(year, 2022)
|
||||||
|
day = min(day, days_in_month(month, year))
|
||||||
|
if cursors[cursor] == "month":
|
||||||
|
month += adjust
|
||||||
|
month = min(max(month, 1), 12)
|
||||||
|
day = min(day, days_in_month(month, year))
|
||||||
|
if cursors[cursor] == "day":
|
||||||
|
day += adjust
|
||||||
|
day = min(max(day, 1), days_in_month(month, year))
|
||||||
|
if cursors[cursor] == "hour":
|
||||||
|
hour += adjust
|
||||||
|
hour %= 24
|
||||||
|
if cursors[cursor] == "minute":
|
||||||
|
minute += adjust
|
||||||
|
minute %= 60
|
||||||
|
|
||||||
|
if set_clock or changed:
|
||||||
|
draw_clock()
|
||||||
|
|
||||||
|
|
||||||
|
# Register the button handling function with the buttons
|
||||||
|
button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_clock():
|
||||||
|
hms = "{:02}:{:02}:{:02}".format(hour, minute, second)
|
||||||
|
ymd = "{:04}/{:02}/{:02}".format(year, month, day)
|
||||||
|
|
||||||
|
hms_width = screen.measure_text(hms, 1.8)
|
||||||
|
hms_offset = int((badger2040.WIDTH / 2) - (hms_width / 2))
|
||||||
|
h_width = screen.measure_text(hms[0:2], 1.8)
|
||||||
|
mi_width = screen.measure_text(hms[3:5], 1.8)
|
||||||
|
mi_offset = screen.measure_text(hms[0:3], 1.8)
|
||||||
|
|
||||||
|
ymd_width = screen.measure_text(ymd, 1.0)
|
||||||
|
ymd_offset = int((badger2040.WIDTH / 2) - (ymd_width / 2))
|
||||||
|
y_width = screen.measure_text(ymd[0:4], 1.0)
|
||||||
|
m_width = screen.measure_text(ymd[5:7], 1.0)
|
||||||
|
m_offset = screen.measure_text(ymd[0:5], 1.0)
|
||||||
|
d_width = screen.measure_text(ymd[8:10], 1.0)
|
||||||
|
d_offset = screen.measure_text(ymd[0:8], 1.0)
|
||||||
|
|
||||||
|
screen.pen(15)
|
||||||
|
screen.clear()
|
||||||
|
screen.pen(0)
|
||||||
|
screen.thickness(5)
|
||||||
|
screen.text(hms, hms_offset, 40, 1.8)
|
||||||
|
screen.thickness(3)
|
||||||
|
screen.text(ymd, ymd_offset, 100, 1.0)
|
||||||
|
|
||||||
|
if set_clock:
|
||||||
|
if cursors[cursor] == "year":
|
||||||
|
screen.line(ymd_offset, 120, ymd_offset + y_width, 120)
|
||||||
|
if cursors[cursor] == "month":
|
||||||
|
screen.line(ymd_offset + m_offset, 120, ymd_offset + m_offset + m_width, 120)
|
||||||
|
if cursors[cursor] == "day":
|
||||||
|
screen.line(ymd_offset + d_offset, 120, ymd_offset + d_offset + d_width, 120)
|
||||||
|
|
||||||
|
if cursors[cursor] == "hour":
|
||||||
|
screen.line(hms_offset, 70, hms_offset + h_width, 70)
|
||||||
|
if cursors[cursor] == "minute":
|
||||||
|
screen.line(hms_offset + mi_offset, 70, hms_offset + mi_offset + mi_width, 70)
|
||||||
|
|
||||||
|
screen.update()
|
||||||
|
|
||||||
|
|
||||||
|
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
|
||||||
|
|
||||||
|
if (year, month, day) == (2021, 1, 1):
|
||||||
|
rtc.datetime((2022, 2, 28, 0, 12, 0, 0, 0))
|
||||||
|
|
||||||
|
last_second = second
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if not set_clock:
|
||||||
|
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
|
||||||
|
if second != last_second:
|
||||||
|
draw_clock()
|
||||||
|
last_second = second
|
||||||
|
time.sleep(0.1)
|
211
badger2040/os/badger2040/conway.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import math
|
||||||
|
import time
|
||||||
|
from random import random
|
||||||
|
|
||||||
|
import machine
|
||||||
|
|
||||||
|
import badger2040
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Program setup
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Global constants
|
||||||
|
CELL_SIZE = 6 # Size of cell in pixels
|
||||||
|
INITIAL_DENSITY = 0.3 # Density of cells at start
|
||||||
|
|
||||||
|
# Create a new Badger and set it to update TURBO
|
||||||
|
screen = badger2040.Badger2040()
|
||||||
|
screen.update_speed(badger2040.UPDATE_TURBO)
|
||||||
|
|
||||||
|
restart = False # should sim be restarted
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Button functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# Button handling function
|
||||||
|
def button(pin):
|
||||||
|
global restart
|
||||||
|
# if 'a' button is pressed, restart the sim
|
||||||
|
if pin == button_a:
|
||||||
|
restart = True
|
||||||
|
|
||||||
|
|
||||||
|
# Set up button
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Screen functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# Remove everything from the screen
|
||||||
|
def init_screen():
|
||||||
|
screen.update_speed(badger2040.UPDATE_NORMAL)
|
||||||
|
screen.pen(15)
|
||||||
|
screen.clear()
|
||||||
|
screen.update()
|
||||||
|
screen.update_speed(badger2040.UPDATE_TURBO)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Classes
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Define a 'cell'
|
||||||
|
class Cell:
|
||||||
|
def __init__(self):
|
||||||
|
self._alive = False
|
||||||
|
|
||||||
|
def make_alive(self):
|
||||||
|
self._alive = True
|
||||||
|
|
||||||
|
def make_dead(self):
|
||||||
|
self._alive = False
|
||||||
|
|
||||||
|
def is_alive(self):
|
||||||
|
return self._alive
|
||||||
|
|
||||||
|
|
||||||
|
# Define the whole board
|
||||||
|
class Board:
|
||||||
|
def __init__(self):
|
||||||
|
self._rows = math.floor(badger2040.WIDTH / CELL_SIZE)
|
||||||
|
self._columns = math.floor(badger2040.HEIGHT / CELL_SIZE)
|
||||||
|
self._grid = [[Cell() for _ in range(self._columns)] for _ in range(self._rows)]
|
||||||
|
|
||||||
|
self._initialise_board()
|
||||||
|
|
||||||
|
# Draw the board to the screen
|
||||||
|
def draw_board(self):
|
||||||
|
row_idx = 0
|
||||||
|
column_idx = 0
|
||||||
|
|
||||||
|
for row in self._grid:
|
||||||
|
column_idx = 0
|
||||||
|
for cell in row:
|
||||||
|
if cell.is_alive():
|
||||||
|
screen.pen(0)
|
||||||
|
else:
|
||||||
|
screen.pen(15)
|
||||||
|
screen.rectangle(
|
||||||
|
row_idx * CELL_SIZE, column_idx * CELL_SIZE, CELL_SIZE, CELL_SIZE
|
||||||
|
)
|
||||||
|
column_idx += 1
|
||||||
|
row_idx += 1
|
||||||
|
|
||||||
|
screen.update()
|
||||||
|
|
||||||
|
# Generate the first iteration of the board
|
||||||
|
def _initialise_board(self):
|
||||||
|
for row in self._grid:
|
||||||
|
for cell in row:
|
||||||
|
if random() <= INITIAL_DENSITY:
|
||||||
|
cell.make_alive()
|
||||||
|
|
||||||
|
# Get the neighbour cells for a given cell
|
||||||
|
def get_neighbours(self, current_row, current_column):
|
||||||
|
# Cells either side of current cell
|
||||||
|
neighbour_min = -1
|
||||||
|
neighbour_max = 2
|
||||||
|
neighbours = []
|
||||||
|
|
||||||
|
for row in range(neighbour_min, neighbour_max):
|
||||||
|
for column in range(neighbour_min, neighbour_max):
|
||||||
|
neighbour_row = current_row + row
|
||||||
|
neighbour_column = current_column + column
|
||||||
|
# Don't count the current cell
|
||||||
|
if not (
|
||||||
|
neighbour_row == current_row and neighbour_column == current_column
|
||||||
|
):
|
||||||
|
# It's a toroidal world so go all the way round if necessary
|
||||||
|
if (neighbour_row) < 0:
|
||||||
|
neighbour_row = self._rows - 1
|
||||||
|
elif (neighbour_row) >= self._rows:
|
||||||
|
neighbour_row = 0
|
||||||
|
|
||||||
|
if (neighbour_column) < 0:
|
||||||
|
neighbour_column = self._columns - 1
|
||||||
|
elif (neighbour_column) >= self._columns:
|
||||||
|
neighbour_column = 0
|
||||||
|
|
||||||
|
neighbours.append(self._grid[neighbour_row][neighbour_column])
|
||||||
|
return neighbours
|
||||||
|
|
||||||
|
# Calculate the next generation
|
||||||
|
def create_next_generation(self):
|
||||||
|
to_alive = []
|
||||||
|
to_dead = []
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
for row in range(len(self._grid)):
|
||||||
|
for column in range(len(self._grid[row])):
|
||||||
|
# Get all the neighours that are alive
|
||||||
|
alive_neighbours = []
|
||||||
|
for neighbour_cell in self.get_neighbours(row, column):
|
||||||
|
if neighbour_cell.is_alive():
|
||||||
|
alive_neighbours.append(neighbour_cell)
|
||||||
|
|
||||||
|
current_cell = self._grid[row][column]
|
||||||
|
# Apply the Conway GoL rules (B3/S23)
|
||||||
|
if current_cell.is_alive():
|
||||||
|
if len(alive_neighbours) < 2 or len(alive_neighbours) > 3:
|
||||||
|
to_dead.append(current_cell)
|
||||||
|
if len(alive_neighbours) == 3 or len(alive_neighbours) == 2:
|
||||||
|
to_alive.append(current_cell)
|
||||||
|
else:
|
||||||
|
if len(alive_neighbours) == 3:
|
||||||
|
to_alive.append(current_cell)
|
||||||
|
|
||||||
|
for cell in to_alive:
|
||||||
|
if not cell.is_alive():
|
||||||
|
# The board has changed since the previous generation
|
||||||
|
changed = True
|
||||||
|
cell.make_alive()
|
||||||
|
|
||||||
|
for cell in to_dead:
|
||||||
|
if cell.is_alive():
|
||||||
|
# The board has changed since the previous generation
|
||||||
|
changed = True
|
||||||
|
cell.make_dead()
|
||||||
|
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Main program loop
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global restart
|
||||||
|
|
||||||
|
init_screen()
|
||||||
|
board = Board()
|
||||||
|
board.draw_board()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# The 'a' button has been pressed so restart sim
|
||||||
|
if restart:
|
||||||
|
init_screen()
|
||||||
|
restart = False
|
||||||
|
board = Board()
|
||||||
|
board.draw_board()
|
||||||
|
time.sleep(0.5)
|
||||||
|
# The board didn't update since the previous generation
|
||||||
|
if not board.create_next_generation():
|
||||||
|
screen.update_speed(badger2040.UPDATE_NORMAL)
|
||||||
|
board.draw_board()
|
||||||
|
screen.update_speed(badger2040.UPDATE_TURBO)
|
||||||
|
time.sleep(5)
|
||||||
|
restart = True
|
||||||
|
# Draw the next generation
|
||||||
|
else:
|
||||||
|
board.draw_board()
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
276
badger2040/os/badger2040/ebook.py
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
import badger2040
|
||||||
|
import machine
|
||||||
|
import time
|
||||||
|
import gc
|
||||||
|
|
||||||
|
|
||||||
|
# **** Put the name of your text file here *****
|
||||||
|
text_file = "book.txt" # File must be on the MicroPython device
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
open(text_file, "r")
|
||||||
|
except OSError:
|
||||||
|
try:
|
||||||
|
# If the specified file doesn't exist,
|
||||||
|
# pre-populate with Wind In The Willows
|
||||||
|
import witw
|
||||||
|
open(text_file, "wb").write(witw.data())
|
||||||
|
del witw
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
# Global Constants
|
||||||
|
WIDTH = badger2040.WIDTH
|
||||||
|
HEIGHT = badger2040.HEIGHT
|
||||||
|
|
||||||
|
ARROW_THICKNESS = 3
|
||||||
|
ARROW_WIDTH = 18
|
||||||
|
ARROW_HEIGHT = 14
|
||||||
|
ARROW_PADDING = 2
|
||||||
|
|
||||||
|
TEXT_PADDING = 4
|
||||||
|
|
||||||
|
TEXT_SIZE = 0.5
|
||||||
|
TEXT_SPACING = int(34 * TEXT_SIZE)
|
||||||
|
TEXT_WIDTH = WIDTH - TEXT_PADDING - TEXT_PADDING - ARROW_WIDTH
|
||||||
|
|
||||||
|
FONTS = ["sans", "gothic", "cursive", "serif"]
|
||||||
|
FONT_THICKNESSES = [2, 1, 1, 2]
|
||||||
|
# ------------------------------
|
||||||
|
# Drawing functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a upward arrow
|
||||||
|
def draw_up(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 4) + padding
|
||||||
|
display.line(x + border, y + height - border,
|
||||||
|
x + (width // 2), y + border)
|
||||||
|
display.line(x + (width // 2), y + border,
|
||||||
|
x + width - border, y + height - border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a downward arrow
|
||||||
|
def draw_down(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.line(x + border, y + border,
|
||||||
|
x + (width // 2), y + height - border)
|
||||||
|
display.line(x + (width // 2), y + height - border,
|
||||||
|
x + width - border, y + border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw the frame of the reader
|
||||||
|
def draw_frame():
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
display.pen(12)
|
||||||
|
display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT)
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(ARROW_THICKNESS)
|
||||||
|
if current_page > 1:
|
||||||
|
draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2),
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2),
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Program setup
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
next_page = True
|
||||||
|
prev_page = False
|
||||||
|
change_font_size = False
|
||||||
|
change_font = False
|
||||||
|
last_offset = 0
|
||||||
|
current_page = 0
|
||||||
|
|
||||||
|
# Create a new Badger and set it to update FAST
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.update_speed(badger2040.UPDATE_FAST)
|
||||||
|
|
||||||
|
# Set up the buttons
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
# Set up the activity LED
|
||||||
|
led = machine.Pin(badger2040.PIN_LED, machine.Pin.OUT)
|
||||||
|
|
||||||
|
offsets = []
|
||||||
|
|
||||||
|
|
||||||
|
# Button handling function
|
||||||
|
def button(pin):
|
||||||
|
global next_page, prev_page, change_font_size, change_font
|
||||||
|
|
||||||
|
if pin == button_down:
|
||||||
|
next_page = True
|
||||||
|
|
||||||
|
if pin == button_up:
|
||||||
|
prev_page = True
|
||||||
|
|
||||||
|
if pin == button_a:
|
||||||
|
change_font_size = True
|
||||||
|
|
||||||
|
if pin == button_b:
|
||||||
|
change_font = True
|
||||||
|
|
||||||
|
|
||||||
|
# Register the button handling function with the buttons
|
||||||
|
button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Render page
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
def render_page():
|
||||||
|
row = 0
|
||||||
|
line = ""
|
||||||
|
pos = ebook.tell()
|
||||||
|
next_pos = pos
|
||||||
|
add_newline = False
|
||||||
|
display.font(FONTS[0])
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Read a full line and split it into words
|
||||||
|
words = ebook.readline().split(" ")
|
||||||
|
|
||||||
|
# Take the length of the first word and advance our position
|
||||||
|
next_word = words[0]
|
||||||
|
if len(words) > 1:
|
||||||
|
next_pos += len(next_word) + 1
|
||||||
|
else:
|
||||||
|
next_pos += len(next_word) # This is the last word on the line
|
||||||
|
|
||||||
|
# Advance our position further if the word contains special characters
|
||||||
|
if '\u201c' in next_word:
|
||||||
|
next_word = next_word.replace('\u201c', '\"')
|
||||||
|
next_pos += 2
|
||||||
|
if '\u201d' in next_word:
|
||||||
|
next_word = next_word.replace('\u201d', '\"')
|
||||||
|
next_pos += 2
|
||||||
|
if '\u2019' in next_word:
|
||||||
|
next_word = next_word.replace('\u2019', '\'')
|
||||||
|
next_pos += 2
|
||||||
|
|
||||||
|
# Rewind the file back from the line end to the start of the next word
|
||||||
|
ebook.seek(next_pos)
|
||||||
|
|
||||||
|
# Strip out any new line characters from the word
|
||||||
|
next_word = next_word.strip()
|
||||||
|
|
||||||
|
# If an empty word is encountered assume that means there was a blank line
|
||||||
|
if len(next_word) == 0:
|
||||||
|
add_newline = True
|
||||||
|
|
||||||
|
# Append the word to the current line and measure its length
|
||||||
|
appended_line = line
|
||||||
|
if len(line) > 0 and len(next_word) > 0:
|
||||||
|
appended_line += " "
|
||||||
|
appended_line += next_word
|
||||||
|
appended_length = display.measure_text(appended_line, TEXT_SIZE)
|
||||||
|
|
||||||
|
# Would this appended line be longer than the text display area, or was a blank line spotted?
|
||||||
|
if appended_length >= TEXT_WIDTH or add_newline:
|
||||||
|
|
||||||
|
# Yes, so write out the line prior to the append
|
||||||
|
print(line)
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(FONT_THICKNESSES[0])
|
||||||
|
display.text(line, TEXT_PADDING, (row * TEXT_SPACING) + (TEXT_SPACING // 2) + TEXT_PADDING, TEXT_SIZE)
|
||||||
|
|
||||||
|
# Clear the line and move on to the next row
|
||||||
|
line = ""
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# Have we reached the end of the page?
|
||||||
|
if (row * TEXT_SPACING) + TEXT_SPACING >= HEIGHT:
|
||||||
|
print("+++++")
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
# Reset the position to the start of the word that made this line too long
|
||||||
|
ebook.seek(pos)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Set the line to the word and advance the current position
|
||||||
|
line = next_word
|
||||||
|
pos = next_pos
|
||||||
|
|
||||||
|
# A new line was spotted, so advance a row
|
||||||
|
if add_newline:
|
||||||
|
print("")
|
||||||
|
row += 1
|
||||||
|
if (row * TEXT_SPACING) + TEXT_SPACING >= HEIGHT:
|
||||||
|
print("+++++")
|
||||||
|
display.update()
|
||||||
|
return
|
||||||
|
add_newline = False
|
||||||
|
else:
|
||||||
|
# The appended line was not too long, so set it as the line and advance the current position
|
||||||
|
line = appended_line
|
||||||
|
pos = next_pos
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Main program loop
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Open the book file
|
||||||
|
ebook = open(text_file, "r")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Was the next page button pressed?
|
||||||
|
if next_page:
|
||||||
|
current_page += 1
|
||||||
|
|
||||||
|
# Is the next page one we've not displayed before?
|
||||||
|
if current_page > len(offsets):
|
||||||
|
offsets.append(ebook.tell()) # Add its start position to the offsets list
|
||||||
|
draw_frame()
|
||||||
|
render_page()
|
||||||
|
next_page = False # Clear the next page button flag
|
||||||
|
|
||||||
|
# Was the previous page button pressed?
|
||||||
|
if prev_page:
|
||||||
|
if current_page > 1:
|
||||||
|
current_page -= 1
|
||||||
|
ebook.seek(offsets[current_page - 1]) # Retrieve the start position of the last page
|
||||||
|
draw_frame()
|
||||||
|
render_page()
|
||||||
|
prev_page = False # Clear the prev page button flag
|
||||||
|
|
||||||
|
if change_font_size:
|
||||||
|
TEXT_SIZE += 0.1
|
||||||
|
if TEXT_SIZE > 0.8:
|
||||||
|
TEXT_SIZE = 0.5
|
||||||
|
TEXT_SPACING = int(34 * TEXT_SIZE)
|
||||||
|
offsets = [0]
|
||||||
|
ebook.seek(0)
|
||||||
|
current_page = 1
|
||||||
|
draw_frame()
|
||||||
|
render_page()
|
||||||
|
change_font_size = False
|
||||||
|
|
||||||
|
if change_font:
|
||||||
|
FONTS.append(FONTS.pop(0))
|
||||||
|
FONT_THICKNESSES.append(FONT_THICKNESSES.pop(0))
|
||||||
|
offsets = [0]
|
||||||
|
ebook.seek(0)
|
||||||
|
current_page = 1
|
||||||
|
draw_frame()
|
||||||
|
render_page()
|
||||||
|
change_font = False
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
141
badger2040/os/badger2040/fonts.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import badger2040
|
||||||
|
import machine
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Global Constants
|
||||||
|
FONT_NAMES = ("sans", "gothic", "cursive", "serif", "serif_italic")
|
||||||
|
|
||||||
|
WIDTH = badger2040.WIDTH
|
||||||
|
HEIGHT = badger2040.HEIGHT
|
||||||
|
|
||||||
|
MENU_TEXT_SIZE = 0.5
|
||||||
|
MENU_SPACING = 20
|
||||||
|
MENU_WIDTH = 84
|
||||||
|
MENU_PADDING = 2
|
||||||
|
|
||||||
|
TEXT_SIZE = 0.8
|
||||||
|
TEXT_INDENT = MENU_WIDTH + 10
|
||||||
|
|
||||||
|
ARROW_THICKNESS = 3
|
||||||
|
ARROW_WIDTH = 18
|
||||||
|
ARROW_HEIGHT = 14
|
||||||
|
ARROW_PADDING = 2
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Drawing functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Draw a upward arrow
|
||||||
|
def draw_up(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 4) + padding
|
||||||
|
display.line(x + border, y + height - border,
|
||||||
|
x + (width // 2), y + border)
|
||||||
|
display.line(x + (width // 2), y + border,
|
||||||
|
x + width - border, y + height - border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a downward arrow
|
||||||
|
def draw_down(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.line(x + border, y + border,
|
||||||
|
x + (width // 2), y + height - border)
|
||||||
|
display.line(x + (width // 2), y + height - border,
|
||||||
|
x + width - border, y + border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw the frame of the reader
|
||||||
|
def draw_frame():
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
display.pen(12)
|
||||||
|
display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT)
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(ARROW_THICKNESS)
|
||||||
|
draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2),
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2),
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw the fonts and menu
|
||||||
|
def draw_fonts():
|
||||||
|
display.font("sans")
|
||||||
|
display.thickness(1)
|
||||||
|
for i in range(len(FONT_NAMES)):
|
||||||
|
name = FONT_NAMES[i]
|
||||||
|
display.pen(0)
|
||||||
|
if i == selected_font:
|
||||||
|
display.rectangle(0, i * MENU_SPACING, MENU_WIDTH, MENU_SPACING)
|
||||||
|
display.pen(15)
|
||||||
|
|
||||||
|
display.text(name, MENU_PADDING, (i * MENU_SPACING) + (MENU_SPACING // 2), MENU_TEXT_SIZE)
|
||||||
|
|
||||||
|
display.font(FONT_NAMES[selected_font])
|
||||||
|
display.thickness(2)
|
||||||
|
|
||||||
|
display.pen(0)
|
||||||
|
display.text("The quick", TEXT_INDENT, 10, TEXT_SIZE)
|
||||||
|
display.text("brown fox", TEXT_INDENT, 32, TEXT_SIZE)
|
||||||
|
display.text("jumped over", TEXT_INDENT, 54, TEXT_SIZE)
|
||||||
|
display.text("the lazy dog.", TEXT_INDENT, 76, TEXT_SIZE)
|
||||||
|
display.text("0123456789", TEXT_INDENT, 98, TEXT_SIZE)
|
||||||
|
display.text("!\"£$%^&*()", TEXT_INDENT, 120, TEXT_SIZE)
|
||||||
|
display.thickness(1)
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Program setup
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
selected_font = 0
|
||||||
|
pressed = False
|
||||||
|
|
||||||
|
# Create a new Badger and set it to update FAST
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.update_speed(badger2040.UPDATE_FAST)
|
||||||
|
|
||||||
|
# Set up the buttons
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
|
||||||
|
# Button handling function
|
||||||
|
def button(pin):
|
||||||
|
global pressed
|
||||||
|
global selected_font
|
||||||
|
|
||||||
|
if not pressed:
|
||||||
|
if pin == button_up:
|
||||||
|
selected_font -= 1
|
||||||
|
if selected_font < 0:
|
||||||
|
selected_font = len(FONT_NAMES) - 1
|
||||||
|
pressed = True
|
||||||
|
return
|
||||||
|
if pin == button_down:
|
||||||
|
selected_font += 1
|
||||||
|
if selected_font >= len(FONT_NAMES):
|
||||||
|
selected_font = 0
|
||||||
|
pressed = True
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# Register the button handling function with the buttons
|
||||||
|
button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Main program loop
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
while True:
|
||||||
|
draw_frame()
|
||||||
|
draw_fonts()
|
||||||
|
|
||||||
|
pressed = False
|
||||||
|
while not pressed:
|
||||||
|
time.sleep(0.1)
|
40
badger2040/os/badger2040/help.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import badger2040
|
||||||
|
import time
|
||||||
|
from badger2040 import WIDTH
|
||||||
|
|
||||||
|
TEXT_SIZE = 0.45
|
||||||
|
LINE_HEIGHT = 16
|
||||||
|
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.pen(0)
|
||||||
|
display.rectangle(0, 0, WIDTH, 16)
|
||||||
|
display.thickness(1)
|
||||||
|
display.pen(15)
|
||||||
|
display.text("badgerOS", 3, 8, 0.4)
|
||||||
|
|
||||||
|
display.pen(0)
|
||||||
|
|
||||||
|
y = 16 + int(LINE_HEIGHT / 2)
|
||||||
|
|
||||||
|
display.thickness(2)
|
||||||
|
display.text("Normal:", 0, y, TEXT_SIZE)
|
||||||
|
display.thickness(1)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("Up / Down - Change launcher page", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("a, b or c - Launch app", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
|
||||||
|
display.thickness(2)
|
||||||
|
display.text("Holding USER button:", 0, y, TEXT_SIZE)
|
||||||
|
display.thickness(1)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("Up / Down - Change font size", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("a - Toggle invert", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
154
badger2040/os/badger2040/image.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import machine
|
||||||
|
import badger2040
|
||||||
|
from badger2040 import WIDTH, HEIGHT
|
||||||
|
|
||||||
|
|
||||||
|
REAMDE = """
|
||||||
|
Images must be 296x128 pixel with 1bit colour depth.
|
||||||
|
|
||||||
|
You can use examples/badger2040/image_converter/convert.py to convert them:
|
||||||
|
|
||||||
|
python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png
|
||||||
|
|
||||||
|
Create a new "images" directory via Thonny, and upload the .bin files there.
|
||||||
|
"""
|
||||||
|
|
||||||
|
OVERLAY_BORDER = 40
|
||||||
|
OVERLAY_SPACING = 20
|
||||||
|
OVERLAY_TEXT_SIZE = 0.5
|
||||||
|
|
||||||
|
TOTAL_IMAGES = 0
|
||||||
|
|
||||||
|
# Try to preload BadgerPunk image
|
||||||
|
try:
|
||||||
|
os.mkdir("images")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import badgerpunk
|
||||||
|
with open("images/badgerpunk.bin", "wb") as f:
|
||||||
|
f.write(badgerpunk.data())
|
||||||
|
f.flush()
|
||||||
|
with open("images/readme.txt", "w") as f:
|
||||||
|
f.write(REAMDE)
|
||||||
|
f.flush()
|
||||||
|
del badgerpunk
|
||||||
|
except (OSError, ImportError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
IMAGES = [f for f in os.listdir("/images") if f.endswith(".bin")]
|
||||||
|
TOTAL_IMAGES = len(IMAGES)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def show_image(n):
|
||||||
|
file = IMAGES[n]
|
||||||
|
name = file.split(".")[0]
|
||||||
|
open("images/{}".format(file), "r").readinto(image)
|
||||||
|
display.image(image)
|
||||||
|
|
||||||
|
if show_info:
|
||||||
|
name_length = display.measure_text(name, 0.5)
|
||||||
|
display.pen(0)
|
||||||
|
display.rectangle(0, HEIGHT - 21, name_length + 11, 21)
|
||||||
|
display.pen(15)
|
||||||
|
display.rectangle(0, HEIGHT - 20, name_length + 10, 20)
|
||||||
|
display.pen(0)
|
||||||
|
display.text(name, 5, HEIGHT - 10, 0.5)
|
||||||
|
|
||||||
|
for i in range(TOTAL_IMAGES):
|
||||||
|
x = 286
|
||||||
|
y = int((128 / 2) - (TOTAL_IMAGES * 10 / 2) + (i * 10))
|
||||||
|
display.pen(0)
|
||||||
|
display.rectangle(x, y, 8, 8)
|
||||||
|
if current_image != i:
|
||||||
|
display.pen(15)
|
||||||
|
display.rectangle(x + 1, y + 1, 6, 6)
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
show_image(current_image)
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if button_up.value():
|
||||||
|
if current_image > 0:
|
||||||
|
current_image -= 1
|
||||||
|
show_image(current_image)
|
||||||
|
if button_down.value():
|
||||||
|
if current_image < TOTAL_IMAGES - 1:
|
||||||
|
current_image += 1
|
||||||
|
show_image(current_image)
|
||||||
|
if button_a.value():
|
||||||
|
show_info = not show_info
|
||||||
|
show_image(current_image)
|
||||||
|
if button_b.value() or button_c.value():
|
||||||
|
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)
|
||||||
|
display.update()
|
||||||
|
time.sleep(4)
|
||||||
|
show_image(current_image)
|
||||||
|
|
||||||
|
time.sleep(0.01)
|
38
badger2040/os/badger2040/info.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import badger2040
|
||||||
|
import time
|
||||||
|
from badger2040 import WIDTH
|
||||||
|
|
||||||
|
TEXT_SIZE = 0.45
|
||||||
|
LINE_HEIGHT = 16
|
||||||
|
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.pen(0)
|
||||||
|
display.rectangle(0, 0, WIDTH, 16)
|
||||||
|
display.thickness(1)
|
||||||
|
display.pen(15)
|
||||||
|
display.text("badgerOS", 3, 8, 0.4)
|
||||||
|
|
||||||
|
display.pen(0)
|
||||||
|
|
||||||
|
y = 16 + int(LINE_HEIGHT / 2)
|
||||||
|
|
||||||
|
display.text("Made by Pimoroni, powered by MicroPython", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("Dual-core RP2040, 133MHz, 264KB RAM", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("2MB Flash (1MB OS, 1MB Storage)", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("296x128 pixel Black/White e-Ink", 0, y, TEXT_SIZE)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
|
||||||
|
display.thickness(2)
|
||||||
|
display.text("For more info:", 0, y, TEXT_SIZE)
|
||||||
|
display.thickness(1)
|
||||||
|
y += LINE_HEIGHT
|
||||||
|
display.text("https://pimoroni.com/badger2040", 0, y, TEXT_SIZE)
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
260
badger2040/os/badger2040/launcher.py
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
import gc
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import math
|
||||||
|
import machine
|
||||||
|
import badger2040
|
||||||
|
from badger2040 import WIDTH
|
||||||
|
import launchericons
|
||||||
|
|
||||||
|
# for e.g. 2xAAA batteries, try max 3.4 min 3.0
|
||||||
|
MAX_BATTERY_VOLTAGE = 4.0
|
||||||
|
MIN_BATTERY_VOLTAGE = 3.2
|
||||||
|
|
||||||
|
|
||||||
|
page = 0
|
||||||
|
font_size = 1
|
||||||
|
inverted = False
|
||||||
|
|
||||||
|
icons = bytearray(launchericons.data())
|
||||||
|
icons_width = 576
|
||||||
|
|
||||||
|
examples = [
|
||||||
|
("_clock", 0),
|
||||||
|
("_fonts", 1),
|
||||||
|
("_ebook", 2),
|
||||||
|
("_image", 3),
|
||||||
|
("_list", 4),
|
||||||
|
("_badge", 5),
|
||||||
|
("_qrgen", 8),
|
||||||
|
("_info", 6),
|
||||||
|
("_help", 7),
|
||||||
|
]
|
||||||
|
|
||||||
|
font_sizes = (0.5, 0.7, 0.9)
|
||||||
|
|
||||||
|
# Approximate center lines for buttons A, B and C
|
||||||
|
centers = (41, 147, 253)
|
||||||
|
|
||||||
|
MAX_PAGE = math.ceil(len(examples) / 3)
|
||||||
|
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
# Inverted. For reasons.
|
||||||
|
button_user = machine.Pin(badger2040.BUTTON_USER, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
|
||||||
|
|
||||||
|
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 get_battery_level():
|
||||||
|
# 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 int(map_value(vbat, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE, 0, 4))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_battery(level, x, y):
|
||||||
|
# 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_disk_usage(x):
|
||||||
|
# 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
|
||||||
|
|
||||||
|
display.image(
|
||||||
|
bytearray(
|
||||||
|
(
|
||||||
|
0b00000000,
|
||||||
|
0b00111100,
|
||||||
|
0b00111100,
|
||||||
|
0b00111100,
|
||||||
|
0b00111000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000000,
|
||||||
|
0b00000001,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
8,
|
||||||
|
8,
|
||||||
|
x,
|
||||||
|
4,
|
||||||
|
)
|
||||||
|
display.pen(15)
|
||||||
|
display.rectangle(x + 10, 3, 80, 10)
|
||||||
|
display.pen(0)
|
||||||
|
display.rectangle(x + 11, 4, 78, 8)
|
||||||
|
display.pen(15)
|
||||||
|
display.rectangle(x + 12, 5, int(76 / 100.0 * f_used), 6)
|
||||||
|
display.text("{:.2f}%".format(f_used), x + 91, 8, 0.4)
|
||||||
|
|
||||||
|
|
||||||
|
def render():
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(2)
|
||||||
|
|
||||||
|
max_icons = min(3, len(examples[(page * 3):]))
|
||||||
|
|
||||||
|
for i in range(max_icons):
|
||||||
|
x = centers[i]
|
||||||
|
label, icon = examples[i + (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])
|
||||||
|
|
||||||
|
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:
|
||||||
|
display.pen(15)
|
||||||
|
display.rectangle(x + 1, y + 1, 6, 6)
|
||||||
|
|
||||||
|
display.pen(0)
|
||||||
|
display.rectangle(0, 0, WIDTH, 16)
|
||||||
|
display.thickness(1)
|
||||||
|
draw_disk_usage(90)
|
||||||
|
draw_battery(get_battery_level(), WIDTH - 22 - 3, 3)
|
||||||
|
display.pen(15)
|
||||||
|
display.text("badgerOS", 3, 8, 0.4)
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
|
||||||
|
def launch(file):
|
||||||
|
for k in locals().keys():
|
||||||
|
if k not in ("gc", "file", "machine"):
|
||||||
|
del locals()[k]
|
||||||
|
gc.collect()
|
||||||
|
try:
|
||||||
|
__import__(file[1:]) # Try to import _[file] (drop underscore prefix)
|
||||||
|
except ImportError:
|
||||||
|
__import__(file) # Failover to importing [_file]
|
||||||
|
machine.reset() # Exit back to launcher
|
||||||
|
|
||||||
|
|
||||||
|
def launch_example(index):
|
||||||
|
try:
|
||||||
|
launch(examples[(page * 3) + index][0])
|
||||||
|
return True
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def button(pin):
|
||||||
|
global page, font_size, inverted
|
||||||
|
|
||||||
|
if button_user.value(): # User button is NOT held down
|
||||||
|
if pin == button_a:
|
||||||
|
launch_example(0)
|
||||||
|
if pin == button_b:
|
||||||
|
launch_example(1)
|
||||||
|
if pin == button_c:
|
||||||
|
launch_example(2)
|
||||||
|
if pin == button_up:
|
||||||
|
if page > 0:
|
||||||
|
page -= 1
|
||||||
|
render()
|
||||||
|
if pin == button_down:
|
||||||
|
if page < MAX_PAGE - 1:
|
||||||
|
page += 1
|
||||||
|
render()
|
||||||
|
else: # User button IS held down
|
||||||
|
if pin == button_up:
|
||||||
|
font_size += 1
|
||||||
|
if font_size == len(font_sizes):
|
||||||
|
font_size = 0
|
||||||
|
render()
|
||||||
|
if pin == button_down:
|
||||||
|
font_size -= 1
|
||||||
|
if font_size < 0:
|
||||||
|
font_size = 0
|
||||||
|
render()
|
||||||
|
if pin == button_a:
|
||||||
|
inverted = not inverted
|
||||||
|
display.invert(inverted)
|
||||||
|
render()
|
||||||
|
|
||||||
|
|
||||||
|
display.update_speed(badger2040.UPDATE_MEDIUM)
|
||||||
|
render()
|
||||||
|
display.update_speed(badger2040.UPDATE_FAST)
|
||||||
|
|
||||||
|
|
||||||
|
# Wait for wakeup button to be released
|
||||||
|
while button_a.value() or button_b.value() or button_c.value() or button_up.value() or button_down.value():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if button_a.value():
|
||||||
|
button(button_a)
|
||||||
|
if button_b.value():
|
||||||
|
button(button_b)
|
||||||
|
if button_c.value():
|
||||||
|
button(button_c)
|
||||||
|
|
||||||
|
if button_up.value():
|
||||||
|
button(button_up)
|
||||||
|
if button_down.value():
|
||||||
|
button(button_down)
|
||||||
|
|
||||||
|
time.sleep(0.01)
|
12
badger2040/os/badger2040/led.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Blinky badger fun!
|
||||||
|
|
||||||
|
import badger2040
|
||||||
|
import time
|
||||||
|
|
||||||
|
badger = badger2040.Badger2040()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
badger.led(255)
|
||||||
|
time.sleep(1)
|
||||||
|
badger.led(0)
|
||||||
|
time.sleep(1)
|
311
badger2040/os/badger2040/list.py
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
import badger2040
|
||||||
|
import machine
|
||||||
|
import time
|
||||||
|
|
||||||
|
# **** Put your list title and contents here *****
|
||||||
|
list_title = "Checklist"
|
||||||
|
list_content = ["Badger", "Badger", "Badger", "Badger", "Badger", "Mushroom", "Mushroom", "Snake"]
|
||||||
|
list_states = [False] * len(list_content)
|
||||||
|
list_file = "checklist.txt"
|
||||||
|
|
||||||
|
|
||||||
|
# Global Constants
|
||||||
|
WIDTH = badger2040.WIDTH
|
||||||
|
HEIGHT = badger2040.HEIGHT
|
||||||
|
|
||||||
|
ARROW_THICKNESS = 3
|
||||||
|
ARROW_WIDTH = 18
|
||||||
|
ARROW_HEIGHT = 14
|
||||||
|
ARROW_PADDING = 2
|
||||||
|
|
||||||
|
MAX_ITEM_CHARS = 26
|
||||||
|
TITLE_TEXT_SIZE = 0.7
|
||||||
|
ITEM_TEXT_SIZE = 0.6
|
||||||
|
ITEM_SPACING = 20
|
||||||
|
|
||||||
|
LIST_START = 40
|
||||||
|
LIST_PADDING = 2
|
||||||
|
LIST_WIDTH = WIDTH - LIST_PADDING - LIST_PADDING - ARROW_WIDTH
|
||||||
|
LIST_HEIGHT = HEIGHT - LIST_START - LIST_PADDING - ARROW_HEIGHT
|
||||||
|
|
||||||
|
|
||||||
|
def save_list():
|
||||||
|
with open(list_file, "w") as f:
|
||||||
|
f.write(list_title + "\n")
|
||||||
|
for i in range(len(list_content)):
|
||||||
|
list_item = list_content[i]
|
||||||
|
if list_states[i]:
|
||||||
|
list_item += " X"
|
||||||
|
f.write(list_item + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Drawing functions
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Draw the list of items
|
||||||
|
def draw_list(items, item_states, start_item, highlighted_item, x, y, width, height, item_height, columns):
|
||||||
|
item_x = 0
|
||||||
|
item_y = 0
|
||||||
|
current_col = 0
|
||||||
|
for i in range(start_item, len(items)):
|
||||||
|
if i == highlighted_item:
|
||||||
|
display.pen(12)
|
||||||
|
display.rectangle(item_x, item_y + y - (item_height // 2), width // columns, item_height)
|
||||||
|
display.pen(0)
|
||||||
|
display.text(items[i], item_x + x + item_height, item_y + y, ITEM_TEXT_SIZE)
|
||||||
|
draw_checkbox(item_x, item_y + y - (item_height // 2), item_height, 15, 0, 2, item_states[i], 2)
|
||||||
|
item_y += item_height
|
||||||
|
if item_y >= height - (item_height // 2):
|
||||||
|
item_x += width // columns
|
||||||
|
item_y = 0
|
||||||
|
current_col += 1
|
||||||
|
if current_col >= columns:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a upward arrow
|
||||||
|
def draw_up(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 4) + padding
|
||||||
|
display.line(x + border, y + height - border,
|
||||||
|
x + (width // 2), y + border)
|
||||||
|
display.line(x + (width // 2), y + border,
|
||||||
|
x + width - border, y + height - border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a downward arrow
|
||||||
|
def draw_down(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.line(x + border, y + border,
|
||||||
|
x + (width // 2), y + height - border)
|
||||||
|
display.line(x + (width // 2), y + height - border,
|
||||||
|
x + width - border, y + border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a left arrow
|
||||||
|
def draw_left(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.line(x + width - border, y + border,
|
||||||
|
x + border, y + (height // 2))
|
||||||
|
display.line(x + border, y + (height // 2),
|
||||||
|
x + width - border, y + height - border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a right arrow
|
||||||
|
def draw_right(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.line(x + border, y + border,
|
||||||
|
x + width - border, y + (height // 2))
|
||||||
|
display.line(x + width - border, y + (height // 2),
|
||||||
|
x + border, y + height - border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a tick
|
||||||
|
def draw_tick(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.line(x + border, y + ((height * 2) // 3),
|
||||||
|
x + (width // 2), y + height - border)
|
||||||
|
display.line(x + (width // 2), y + height - border,
|
||||||
|
x + width - border, y + border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a cross
|
||||||
|
def draw_cross(x, y, width, height, thickness, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.line(x + border, y + border, x + width - border, y + height - border)
|
||||||
|
display.line(x + width - border, y + border, x + border, y + height - border)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw a checkbox with or without a tick
|
||||||
|
def draw_checkbox(x, y, size, background, foreground, thickness, tick, padding):
|
||||||
|
border = (thickness // 2) + padding
|
||||||
|
display.pen(background)
|
||||||
|
display.rectangle(x + border, y + border, size - (border * 2), size - (border * 2))
|
||||||
|
display.pen(foreground)
|
||||||
|
display.thickness(thickness)
|
||||||
|
display.line(x + border, y + border, x + size - border, y + border)
|
||||||
|
display.line(x + border, y + border, x + border, y + size - border)
|
||||||
|
display.line(x + size - border, y + border, x + size - border, y + size - border)
|
||||||
|
display.line(x + border, y + size - border, x + size - border, y + size - border)
|
||||||
|
if tick:
|
||||||
|
draw_tick(x, y, size, size, thickness, 2 + border)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Program setup
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
update = True
|
||||||
|
needs_save = False
|
||||||
|
current_item = 0
|
||||||
|
items_per_page = 0
|
||||||
|
|
||||||
|
# Create a new Badger and set it to update FAST
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
display.update_speed(badger2040.UPDATE_FAST)
|
||||||
|
|
||||||
|
# Set up the buttons
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
# Find out what the longest item is
|
||||||
|
longest_item = 0
|
||||||
|
for i in range(len(list_content)):
|
||||||
|
while True:
|
||||||
|
item = list_content[i]
|
||||||
|
item_length = display.measure_text(item, ITEM_TEXT_SIZE)
|
||||||
|
if item_length > 0 and item_length > LIST_WIDTH - ITEM_SPACING:
|
||||||
|
list_content[i] = item[:-1]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
longest_item = max(longest_item, display.measure_text(list_content[i], ITEM_TEXT_SIZE))
|
||||||
|
|
||||||
|
|
||||||
|
# And use that to calculate the number of columns we can fit onscreen and how many items that would give
|
||||||
|
list_columns = 1
|
||||||
|
while longest_item + ITEM_SPACING < (LIST_WIDTH // (list_columns + 1)):
|
||||||
|
list_columns += 1
|
||||||
|
|
||||||
|
items_per_page = ((LIST_HEIGHT // ITEM_SPACING) + 1) * list_columns
|
||||||
|
|
||||||
|
|
||||||
|
# Button handling function
|
||||||
|
def button(pin):
|
||||||
|
global update, current_item, needs_save
|
||||||
|
|
||||||
|
if len(list_content) > 0 and not update:
|
||||||
|
if pin == button_a:
|
||||||
|
if current_item > 0:
|
||||||
|
current_item = max(current_item - (items_per_page) // list_columns, 0)
|
||||||
|
update = True
|
||||||
|
return
|
||||||
|
if pin == button_b:
|
||||||
|
list_states[current_item] = not list_states[current_item]
|
||||||
|
needs_save = True
|
||||||
|
update = True
|
||||||
|
return
|
||||||
|
if pin == button_c:
|
||||||
|
if current_item < len(list_content) - 1:
|
||||||
|
current_item = min(current_item + (items_per_page) // list_columns, len(list_content) - 1)
|
||||||
|
update = True
|
||||||
|
return
|
||||||
|
if pin == button_up:
|
||||||
|
if current_item > 0:
|
||||||
|
current_item -= 1
|
||||||
|
update = True
|
||||||
|
return
|
||||||
|
if pin == button_down:
|
||||||
|
if current_item < len(list_content) - 1:
|
||||||
|
current_item += 1
|
||||||
|
update = True
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# Register the button handling function with the buttons
|
||||||
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Main program loop
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(list_file, "r") as f:
|
||||||
|
list_content = f.read().strip().split("\n")
|
||||||
|
list_title = list_content.pop(0)
|
||||||
|
list_states = [False] * len(list_content)
|
||||||
|
for i in range(len(list_content)):
|
||||||
|
list_content[i] = list_content[i].strip()
|
||||||
|
if list_content[i].endswith(" X"):
|
||||||
|
list_states[i] = True
|
||||||
|
list_content[i] = list_content[i][:-2]
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
save_list()
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if needs_save:
|
||||||
|
save_list()
|
||||||
|
needs_save = False
|
||||||
|
|
||||||
|
if update:
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
|
||||||
|
display.pen(12)
|
||||||
|
display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT)
|
||||||
|
display.rectangle(0, HEIGHT - ARROW_HEIGHT, WIDTH, ARROW_HEIGHT)
|
||||||
|
|
||||||
|
y = LIST_PADDING + 12
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(3)
|
||||||
|
display.text(list_title, LIST_PADDING, y, TITLE_TEXT_SIZE)
|
||||||
|
|
||||||
|
y += 12
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(2)
|
||||||
|
display.line(LIST_PADDING, y, WIDTH - LIST_PADDING - ARROW_WIDTH, y)
|
||||||
|
|
||||||
|
if len(list_content) > 0:
|
||||||
|
page_item = 0
|
||||||
|
if items_per_page > 0:
|
||||||
|
page_item = (current_item // items_per_page) * items_per_page
|
||||||
|
|
||||||
|
# Draw the list
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(2)
|
||||||
|
draw_list(list_content, list_states, page_item, current_item, LIST_PADDING, LIST_START,
|
||||||
|
LIST_WIDTH, LIST_HEIGHT, ITEM_SPACING, list_columns)
|
||||||
|
|
||||||
|
# Draw the interaction button icons
|
||||||
|
display.pen(0)
|
||||||
|
display.thickness(ARROW_THICKNESS)
|
||||||
|
|
||||||
|
# Previous item
|
||||||
|
if current_item > 0:
|
||||||
|
draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2),
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
|
||||||
|
# Next item
|
||||||
|
if current_item < (len(list_content) - 1):
|
||||||
|
draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2),
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
|
||||||
|
# Previous column
|
||||||
|
if current_item > 0:
|
||||||
|
draw_left((WIDTH // 7) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
|
||||||
|
# Next column
|
||||||
|
if current_item < (len(list_content) - 1):
|
||||||
|
draw_right(((WIDTH * 6) // 7) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
|
||||||
|
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
|
||||||
|
if list_states[current_item]:
|
||||||
|
# Tick off item
|
||||||
|
draw_cross((WIDTH // 2) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
|
||||||
|
ARROW_HEIGHT, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
else:
|
||||||
|
# Untick item
|
||||||
|
draw_tick((WIDTH // 2) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
|
||||||
|
ARROW_HEIGHT, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
|
||||||
|
else:
|
||||||
|
# Say that the list is empty
|
||||||
|
empty_text = "Nothing Here"
|
||||||
|
text_length = display.measure_text(empty_text, ITEM_TEXT_SIZE)
|
||||||
|
display.text(empty_text, ((LIST_PADDING + LIST_WIDTH) - text_length) // 2, (LIST_HEIGHT // 2) + LIST_START - (ITEM_SPACING // 4), ITEM_TEXT_SIZE)
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
display.update_speed(badger2040.UPDATE_TURBO)
|
||||||
|
update = False
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
54
badger2040/os/badger2040/micropython-builtins.cmake
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
function (convert_image TARGET IMAGE)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../modules/${IMAGE}.py
|
||||||
|
|
||||||
|
COMMAND
|
||||||
|
cd ${CMAKE_CURRENT_LIST_DIR}/assets && python3 ../../../../examples/badger2040/image_converter/convert.py --out_dir ${CMAKE_CURRENT_BINARY_DIR}/../modules --py ${IMAGE}.png
|
||||||
|
|
||||||
|
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/assets/${IMAGE}.png
|
||||||
|
)
|
||||||
|
target_sources(${TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/../modules/${IMAGE}.py)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function (convert_raw TARGET SRC DST)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py
|
||||||
|
|
||||||
|
COMMAND
|
||||||
|
cd ${CMAKE_CURRENT_LIST_DIR}/assets && python3 ../../../../examples/badger2040/image_converter/data_to_py.py ${CMAKE_CURRENT_LIST_DIR}/assets/${SRC} ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py
|
||||||
|
|
||||||
|
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/assets/${SRC}
|
||||||
|
)
|
||||||
|
target_sources(${TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function (copy_module TARGET SRC DST)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py
|
||||||
|
|
||||||
|
COMMAND
|
||||||
|
cp ${SRC} ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py
|
||||||
|
|
||||||
|
DEPENDS ${src}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(${TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
convert_image(usermod_badger2040 badge_image)
|
||||||
|
convert_image(usermod_badger2040 badgerpunk)
|
||||||
|
convert_image(usermod_badger2040 launchericons)
|
||||||
|
|
||||||
|
convert_raw(usermod_badger2040 289-0-wind-in-the-willows-abridged.txt witw)
|
||||||
|
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/assets/boot.py boot)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/launcher.py _launcher)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/clock.py _clock)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/fonts.py _fonts)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/ebook.py _ebook)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/image.py _image)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/list.py _list)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/badge.py _badge)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/help.py _help)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/info.py _info)
|
||||||
|
copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/qrgen.py _qrgen)
|
65
badger2040/os/badger2040/pin_interrupt.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import badger2040
|
||||||
|
import machine
|
||||||
|
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
|
||||||
|
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
||||||
|
|
||||||
|
display.thickness(10)
|
||||||
|
display.pen(0)
|
||||||
|
display.line(0, 5, 295, 5)
|
||||||
|
display.line(0, 123, 295, 123)
|
||||||
|
|
||||||
|
display.thickness(1)
|
||||||
|
for x in range(14):
|
||||||
|
display.line(x * 20, 10, x * 20, 118)
|
||||||
|
|
||||||
|
display.line(0, 0, 295, 127)
|
||||||
|
display.line(0, 127, 295, 0)
|
||||||
|
|
||||||
|
display.font("sans")
|
||||||
|
display.thickness(5)
|
||||||
|
display.text("Hello World", 10, 30, 1.0)
|
||||||
|
display.pen(7)
|
||||||
|
display.text("Hello World", 10, 60, 1.0)
|
||||||
|
display.pen(11)
|
||||||
|
display.text("Hello World", 10, 90, 1.0)
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
dirty = False
|
||||||
|
pressed = None
|
||||||
|
|
||||||
|
|
||||||
|
def button(pin):
|
||||||
|
global dirty, pressed
|
||||||
|
if pin == button_a:
|
||||||
|
pressed = "Button A"
|
||||||
|
dirty = True
|
||||||
|
return
|
||||||
|
if pin == button_b:
|
||||||
|
pressed = "Button B"
|
||||||
|
dirty = True
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button)
|
||||||
|
|
||||||
|
# This breaks Thonny, since it's no longer possible to Stop the code
|
||||||
|
# need to press the reset button on the board...
|
||||||
|
# It will also crash your USB bus, probably, your whole bus...
|
||||||
|
# @micropython.asm_thumb
|
||||||
|
# def lightsleep():
|
||||||
|
# wfi()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if dirty:
|
||||||
|
display.pen(15)
|
||||||
|
display.clear()
|
||||||
|
display.pen(0)
|
||||||
|
display.text(pressed, 10, 60, 2.0)
|
||||||
|
display.update()
|
||||||
|
dirty = False
|
||||||
|
# machine.lightsleep() # Currently imposible to wake from this on IRQ
|
70
badger2040/os/badger2040/qrgen.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import badger2040
|
||||||
|
import qrcode
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
# Open the qrcode file
|
||||||
|
try:
|
||||||
|
text = open("qrcode.txt", "r")
|
||||||
|
except OSError:
|
||||||
|
text = open("qrcode.txt", "w")
|
||||||
|
text.write("""https://pimoroni.com/badger2040
|
||||||
|
Badger 2040
|
||||||
|
* 296x128 1-bit e-ink
|
||||||
|
* six user buttons
|
||||||
|
* user LED
|
||||||
|
* 2MB QSPI flash
|
||||||
|
|
||||||
|
Scan this code to learn
|
||||||
|
more about Badger 2040.
|
||||||
|
""")
|
||||||
|
text.flush()
|
||||||
|
text.seek(0)
|
||||||
|
|
||||||
|
|
||||||
|
lines = text.read().strip().split("\n")
|
||||||
|
code_text = lines.pop(0)
|
||||||
|
title_text = lines.pop(0)
|
||||||
|
detail_text = lines
|
||||||
|
|
||||||
|
display = badger2040.Badger2040()
|
||||||
|
code = qrcode.QRCode()
|
||||||
|
|
||||||
|
|
||||||
|
def measure_qr_code(size, code):
|
||||||
|
w, h = code.get_size()
|
||||||
|
module_size = int(size / w)
|
||||||
|
return module_size * w, module_size
|
||||||
|
|
||||||
|
|
||||||
|
def draw_qr_code(ox, oy, size, code):
|
||||||
|
size, module_size = measure_qr_code(size, code)
|
||||||
|
display.pen(15)
|
||||||
|
display.rectangle(ox, oy, size, size)
|
||||||
|
display.pen(0)
|
||||||
|
for x in range(size):
|
||||||
|
for y in range(size):
|
||||||
|
if code.get_module(x, y):
|
||||||
|
display.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size)
|
||||||
|
|
||||||
|
|
||||||
|
code.set_text(code_text)
|
||||||
|
size, _ = measure_qr_code(128, code)
|
||||||
|
left = top = int((badger2040.HEIGHT / 2) - (size / 2))
|
||||||
|
draw_qr_code(left, top, 128, code)
|
||||||
|
|
||||||
|
left = 128 + 5
|
||||||
|
|
||||||
|
display.thickness(2)
|
||||||
|
display.text(title_text, left, 20, 0.5)
|
||||||
|
display.thickness(1)
|
||||||
|
|
||||||
|
top = 40
|
||||||
|
for line in detail_text:
|
||||||
|
display.text(line, left, top, 0.4)
|
||||||
|
top += 10
|
||||||
|
|
||||||
|
display.update()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(1.0)
|
58
badger2040/poetry.lock
generated
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "9.1.1"
|
||||||
|
description = "Python Imaging Library (Fork)"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"]
|
||||||
|
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "1.1"
|
||||||
|
python-versions = "^3.8"
|
||||||
|
content-hash = "8a3b1f7b4c4af4b58e7bcb3333c009c0da1516d59db4332afeca1ce6a360c1ad"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
pillow = [
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"},
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"},
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"},
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"},
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"},
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"},
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"},
|
||||||
|
{file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"},
|
||||||
|
{file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"},
|
||||||
|
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"},
|
||||||
|
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"},
|
||||||
|
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"},
|
||||||
|
{file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"},
|
||||||
|
{file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"},
|
||||||
|
{file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"},
|
||||||
|
{file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"},
|
||||||
|
{file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"},
|
||||||
|
{file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"},
|
||||||
|
{file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"},
|
||||||
|
{file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"},
|
||||||
|
{file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"},
|
||||||
|
{file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"},
|
||||||
|
{file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"},
|
||||||
|
{file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"},
|
||||||
|
]
|
15
badger2040/pyproject.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "badger2040"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "scripts et libs pour badger2040"
|
||||||
|
authors = ["Your Name <you@example.com>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.8"
|
||||||
|
Pillow = "^9.1.1"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|