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"
|