Compare commits
10 Commits
efb10c9127
...
main
Author | SHA1 | Date | |
---|---|---|---|
30697ec832 | |||
39f5e18e3d | |||
a563aed66e | |||
f76f6aeeec | |||
ca775f3773 | |||
2f44cc9ff4 | |||
e24f749ce4 | |||
21dc360c96 | |||
d1bf15eb87 | |||
320333cc16 |
15
README.md
@ -1,3 +1,16 @@
|
|||||||
# Raspberry pi
|
# Raspberry pi
|
||||||
|
scripts et tuto raspberry pi.
|
||||||
|
|
||||||
scripts et tuto raspberry pi
|
## Docs
|
||||||
|
- [setup](/doc/setup.md)
|
||||||
|
- [pi-hole (bloqueur de pub)](/doc/pihole.md)
|
||||||
|
- [phat-beat](/doc/phat-beat.md)
|
||||||
|
|
||||||
|
## Commandes courantes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# eteindre
|
||||||
|
sudo shutdown -h now
|
||||||
|
# redémarrer
|
||||||
|
sudo shutdown -r now
|
||||||
|
```
|
||||||
|
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"
|
23
doc/phat-beat.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Phat Beat / Pirate Radio
|
||||||
|
|
||||||
|
Hat audio par Piromoni
|
||||||
|
|
||||||
|
- [getting started](https://learn.pimoroni.com/tutorial/sandyj/getting-started-with-phat-beat)
|
||||||
|
|
||||||
|
## Internet Radio via VLC
|
||||||
|
|
||||||
|
Le [tuto Piromoni](https://learn.pimoroni.com/tutorial/sandyj/internet-radio-on-your-pirate-radio) ne fonctionne pas. On obtient le message :
|
||||||
|
> --- Warning ---
|
||||||
|
>
|
||||||
|
> The VLC Radio installer
|
||||||
|
> does not work on this version of Raspbian.
|
||||||
|
|
||||||
|
Il faut cloner [le dépot git](https://github.com/pimoroni/phat-beat) et lancer le script via `sudo bash setup.sh` depuis le dépot cloné.
|
||||||
|
|
||||||
|
La playlist se trouve à /etc/vlcd/default.m3u
|
||||||
|
|
||||||
|
Démarrer / stopper le service : `sudo service vlcd stop`
|
||||||
|
|
||||||
|
## à voir
|
||||||
|
|
||||||
|
- [juke box python + vlc + tornado](https://github.com/lionaneesh/RasPod)
|
151
doc/pihole.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# Pi-Hole pour filtrer le réseau
|
||||||
|
|
||||||
|
|
||||||
|
## commandes d'administration
|
||||||
|
|
||||||
|
[doc des commandes](https://docs.pi-hole.net/core/pihole-command/)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pihole -a -p # Changer le mot de passe WebUI
|
||||||
|
pihole status # afficher le statut
|
||||||
|
pihole -up # mise à jour de pi-hole
|
||||||
|
pihole enable # Activer PiHole
|
||||||
|
pihole disable # Désactiver PiHole en permanence
|
||||||
|
pihole disable 10m # Désactiver PiHole pendant 10 minutes
|
||||||
|
pihole disable 60s # Désactiver PiHole pendant 1 min
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Forcer l'usage de piHole
|
||||||
|
|
||||||
|
https://www.geekzone.fr/2020/05/13/pi-hole-5-un-trou-noir-qui-protege-votre-logement-des-pubs/
|
||||||
|
|
||||||
|
https://discourse.pi-hole.net/t/how-do-i-use-pi-holes-built-in-dhcp-server-and-why-would-i-want-to/3026
|
||||||
|
|
||||||
|
> Some routers (and even some Internet Service Providers) do not let you change these settings. In this case, using Pi-hole’s DHCP server is very advantageous because you can still get automatic, network-wide ad ad blocking even if you can’t make the necessary changes on existing hardware.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
> Bonjour messieurs, j'infirme ce qui a été dit avant, on peut changer les DNS de la livebox mais il faut désactiver le serveur DHCP de la box.
|
||||||
|
>
|
||||||
|
> Par exemple, j'utilise Pi-hole sur un ubuntu (rasperry pi fera l'affaire), sur ce pi-hole j'ai activé le serveur dhcp pi-hole , qui distribue les adresses DNS, et fait office de DNS avec débordements sur celles d'Orange mais j'aurai déborder vers google aussi.
|
||||||
|
>
|
||||||
|
> En méthodologie, il faut activer d'abord le dhcp de la livebox et ainsi fixer l'adresse IP de la livebox (pour moi 10.0.0.1), j'affecte ensuite une ip fixe sur le rasperri pi-hole , puis je désactive le dhcp livebox et enfin j'active celui du rasperry pi-hole avec passerelle sur le 10.0.0.1. J'ajoute un DNS secondary dans les fichiers config pihole et je mets la livebox , au cas où le rasperry ait un souci de ralentissement important.
|
||||||
|
|
||||||
|
https://mediacenterz.com/tutoriel-complete-pi-hole-bloqueur-dannonces-pour-toute-la-maison/ :
|
||||||
|
|
||||||
|
> Pour bloquer les annonces au niveau du réseau, ce qui signifie que tous les appareils connectés à votre réseau domestique ne verront aucune annonce, vous devrez modifier manuellement les adresses IP de votre serveur DNS sur votre routeur. Alors que de nombreux routeurs autorisent la configuration manuelle des serveurs DNS, certains ne rendent pas cette option avancée disponible. Les micrologiciels de routeur personnalisés, tels que DD-WRT, OpenWRT et Tomato, permettent d’ouvrir cette option parmi d’autres. Par conséquent, si vous ne voyez pas d’option permettant de modifier les serveurs de noms DNS, envisagez de passer à l’un des microprogrammes de routeur de remplacement gratuit pris en charge.
|
||||||
|
>
|
||||||
|
>L’avantage ici est qu’il n’y a qu’un seul endroit où vous aurez besoin de mettre à jour l’adresse IP de votre serveur DNS au lieu de chaque appareil. Cependant, il y a quelques inconvénients:
|
||||||
|
>
|
||||||
|
>Le suivi par hôte sera indisponible. Toutes les demandes adressées à PiHole apparaîtront comme si elles venaient de votre routeur. Mon opinion personnelle est que ce n’est pas un gros problème pour un utilisateur domestique typique. Je ne l’utilise pas. Mais si vous en avez absolument besoin
|
||||||
|
Vous ne pourrez pas vous connecter aux périphériques avec leurs noms d’hôte car PiHole ne peut pas résoudre les noms d’hôte. Encore une fois, pas un gros problème pour un utilisateur à domicile typique à mon avis.
|
||||||
|
Si les deux inconvénients ci-dessus sont des pactes pour vous, vous pouvez les surmonter partiellement en utilisant le fichier PiHole Hosts ou en publiant complètement l’adresse IP de PiHole via Dnsmasq dans un routeur (si cette fonction est prise en charge).
|
||||||
|
>
|
||||||
|
>Notez que si vous choisissez cette méthode, vous devrez renouveler les baux DHCP fournis par le routeur. Pour ce faire, le plus simple consiste à redémarrer le routeur.
|
||||||
|
|
||||||
|
## faire tourner d'autres sites avec Apache
|
||||||
|
|
||||||
|
https://discourse.pi-hole.net/t/migrating-pi-hole-from-lighttpd-to-apache/152/14 :
|
||||||
|
|
||||||
|
> I just installed pihole on ubuntu server and because I already use Apache & PHP I uninstalled lighttpd.
|
||||||
|
I have several virtual sites on Apache so I added also pihole; all work without problems.
|
||||||
|
|
||||||
|
> Here it is my config if anybody need to replicate for his use:
|
||||||
|
edit: /etc/apache2/sites-available/000-default.conf
|
||||||
|
|
||||||
|
```
|
||||||
|
#=== pihole WEBSITE ===
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
ServerName pihole
|
||||||
|
ServerAlias pi.hole
|
||||||
|
|
||||||
|
DocumentRoot /var/www/html/dns
|
||||||
|
<Directory /var/www/html/dns/>
|
||||||
|
Options FollowSymLinks MultiViews
|
||||||
|
AllowOverride all
|
||||||
|
Order deny,allow
|
||||||
|
allow from all
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/pihole_error.log
|
||||||
|
LogLevel warn
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/pihole_access.log combined
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
#=== site1 WEBSITE ===
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
ServerName site1
|
||||||
|
|
||||||
|
DocumentRoot /var/www/html/site1
|
||||||
|
<Directory /var/www/html/site1/>
|
||||||
|
Options FollowSymLinks MultiViews
|
||||||
|
AllowOverride all
|
||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
allow from 127.0.0.1
|
||||||
|
allow from 10.22.22.0/24
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/site1_error.log
|
||||||
|
LogLevel warn
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/site1_access.log combined
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
#==== site2 WEBSITE ====
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
ServerName webtv
|
||||||
|
|
||||||
|
DocumentRoot /var/www/html/site2
|
||||||
|
<Directory /var/www/html/site2/>
|
||||||
|
Options FollowSymLinks MultiViews
|
||||||
|
AllowOverride all
|
||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
allow from 10.22.22.0/24
|
||||||
|
allow from 127.0.0.1
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/site2_error.log
|
||||||
|
LogLevel warn
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/site2_access.log combined
|
||||||
|
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
> edit your server hosts file so you can access any website you need from server,
|
||||||
|
( if you host your custom files for blocking):
|
||||||
|
|
||||||
|
```
|
||||||
|
# /etc/hosts
|
||||||
|
127.0.0.1 localhost
|
||||||
|
127.0.1.1 ubuntu1
|
||||||
|
127.0.1.2 pihole
|
||||||
|
127.0.1.3 site1
|
||||||
|
127.0.1.4 site2
|
||||||
|
|
||||||
|
10.22.22.16 site1
|
||||||
|
10.22.22.16 site2
|
||||||
|
10.22.22.16 pihole
|
||||||
|
10.22.22.16 ubuntu1
|
||||||
|
```
|
||||||
|
|
||||||
|
> copy all web files for pihole ( move it from /var/www/html/ ) and also content from pihole in dns:
|
||||||
|
it will look like this:
|
||||||
|
|
||||||
|
/var/www/html/dns/admin/...
|
||||||
|
/var/www/html/dns/pihole/...
|
||||||
|
/var/www/html/dns/blockingpage.css
|
||||||
|
/var/www/html/dns/index.js
|
||||||
|
/var/www/html/dns/index.php
|
||||||
|
/var/www/html/site1/...
|
||||||
|
/var/www/html/site2/...
|
||||||
|
|
||||||
|
> If I did not forgot something this is all you need to change to have pihole working in Apache, so now reload/restart apache.
|
37
doc/setup.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# SETUP nouveau raspberry pi
|
||||||
|
|
||||||
|
## Tutos
|
||||||
|
|
||||||
|
- [Installer un pi sans écran ni clavier](https://learn.pimoroni.com/tutorial/sandyj/setting-up-a-headless-pi)
|
||||||
|
- [installer log2ram pour prolonger la vie de la carte SD](https://github.com/azlux/log2ram)
|
||||||
|
|
||||||
|
### Créer un utilisateur sudoer
|
||||||
|
Adapté depuis [ce tuto](https://www.raspberrypi.org/forums/viewtopic.php?t=169079) :
|
||||||
|
|
||||||
|
```shell
|
||||||
|
NEW_USER="jducastel"
|
||||||
|
# créer l'utilisateur + dossier home
|
||||||
|
sudo adduser $NEW_USER
|
||||||
|
# l'ajouter aux mêmes groupes que l'utilisateur "pi"
|
||||||
|
sudo usermod $NEW_USER -a -G pi,adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,netdev,spi,i2c,gpio
|
||||||
|
# pour ne pas avoir à taper le mdp en tant que sudo,
|
||||||
|
# on clone le fichier concernant l'utilisateur "pi" dans le dossier inclus
|
||||||
|
sudo cp /etc/sudoers.d/010_pi-nopasswd /etc/sudoers.d/$NEW_USER
|
||||||
|
# remplacer "pi" par le nom de l'utilisateur
|
||||||
|
sudo nano /etc/sudoers.d/$NEW_USER
|
||||||
|
```
|
||||||
|
|
||||||
|
### changer le hostname
|
||||||
|
```shell
|
||||||
|
sudo hostnamectl set-hostname NEW_HOSTNAME
|
||||||
|
# remplacer "raspberry" par le nouveau nom pour 127.0.1.1
|
||||||
|
sudo nano /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
### passer de bash à zshell
|
||||||
|
```shell
|
||||||
|
sudo apt-get update && sudo apt-get install zsh
|
||||||
|
# Edit your passwd configuration file to tell which shell to use for user pi :
|
||||||
|
# change /bin/bash for /bin/zsh for your user
|
||||||
|
sudo nano /etc/passwd
|
||||||
|
```
|
@ -6,30 +6,25 @@ sur raspberry pi + sense hat
|
|||||||
|
|
||||||
principe : saisie d'un message en morse via le joystick de la sense hat
|
principe : saisie d'un message en morse via le joystick de la sense hat
|
||||||
la grille LED permet d'avoir un retour / analyse de ce qui est saisi
|
la grille LED permet d'avoir un retour / analyse de ce qui est saisi
|
||||||
|
une fois le message construit, transmission via la matrice LED
|
||||||
|
|
||||||
utilisation du joystick de la sense hat
|
utilisation du joystick de la sense hat
|
||||||
gauche = saisir un point
|
haut = saisir un point
|
||||||
droit = saisir un trait
|
bas = saisir un trait
|
||||||
haut = valider (selon le contexte : lettre, mot, message), ajouter un espace
|
droite = valider (selon le contexte : lettre, mot, message), ajouter un espace
|
||||||
bas = corriger / annuler la dernière entrée
|
gauche = corriger / annuler la dernière entrée
|
||||||
|
|
||||||
utilise la lib morse-talk pour le decodage / encodage
|
utilise la lib morse-talk pour le decodage / encodage. pour l'installation :
|
||||||
$ pip install morse-talk
|
$ pip install morse-talk
|
||||||
|
|
||||||
|
utilisation :
|
||||||
|
$ python sense_morse.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sense_hat import SenseHat, ACTION_PRESSED, ACTION_HELD, ACTION_RELEASED
|
from sense_hat import SenseHat, ACTION_PRESSED, ACTION_HELD, ACTION_RELEASED
|
||||||
from signal import pause
|
from signal import pause
|
||||||
import time, morse_talk
|
import time, morse_talk
|
||||||
|
|
||||||
|
|
||||||
DOT = '.'
|
|
||||||
DASH = '-'
|
|
||||||
PAUSE = ' '
|
|
||||||
END_CHAR = ' '
|
|
||||||
END_WORD = ' '
|
|
||||||
|
|
||||||
|
|
||||||
class MorseSenseHatKeyer:
|
class MorseSenseHatKeyer:
|
||||||
|
|
||||||
green = [0, 255, 0]
|
green = [0, 255, 0]
|
||||||
@ -38,16 +33,19 @@ class MorseSenseHatKeyer:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sense = SenseHat()
|
self.sense = SenseHat()
|
||||||
|
# association des evenements générés par le joystick de la SenseHat
|
||||||
|
# aux méthodes de traitement internes
|
||||||
self.sense.stick.direction_up = self.pushed_up
|
self.sense.stick.direction_up = self.pushed_up
|
||||||
self.sense.stick.direction_down = self.pushed_down
|
self.sense.stick.direction_down = self.pushed_down
|
||||||
self.sense.stick.direction_left = self.pushed_left
|
self.sense.stick.direction_left = self.pushed_left
|
||||||
self.sense.stick.direction_right = self.pushed_right
|
self.sense.stick.direction_right = self.pushed_right
|
||||||
self.sense.stick.direction_any = self.refresh
|
self.sense.stick.direction_any = self.refresh
|
||||||
self.message_ascii = ''
|
# initalisation des variables de traitement
|
||||||
self.current_morse_char = ''
|
self.message_ascii = '' # message construit sous forme ASCII
|
||||||
|
self.current_morse_char = '' # caractère morse en cours de construction
|
||||||
|
|
||||||
def pushed_left(self, event):
|
def pushed_left(self, event):
|
||||||
""" correction """
|
""" correction (réinitialise caractère courant) """
|
||||||
# correction
|
# correction
|
||||||
if event.action != ACTION_RELEASED:
|
if event.action != ACTION_RELEASED:
|
||||||
self.current_morse_char = ''
|
self.current_morse_char = ''
|
||||||
@ -62,48 +60,49 @@ class MorseSenseHatKeyer:
|
|||||||
self.validate_char()
|
self.validate_char()
|
||||||
|
|
||||||
def pushed_down(self, event):
|
def pushed_down(self, event):
|
||||||
""" ajout trait """
|
""" ajout d'un trait """
|
||||||
if event.action == ACTION_HELD:
|
if event.action == ACTION_HELD: # tant que le bouton est pressé
|
||||||
self.show_dash()
|
self.show_dash() # affichage d'un trait
|
||||||
if event.action == ACTION_RELEASED:
|
if event.action == ACTION_RELEASED: # au relachement
|
||||||
self.add_dash()
|
self.add_dash() # ajout du trait si possible
|
||||||
|
|
||||||
def pushed_up(self, event):
|
def pushed_up(self, event):
|
||||||
""" ajout point """
|
""" ajout point """
|
||||||
if event.action == ACTION_HELD:
|
if event.action == ACTION_HELD: # tant que le bouton est pressé
|
||||||
self.show_dot()
|
self.show_dot() # affichage d'un point
|
||||||
if event.action == ACTION_RELEASED:
|
if event.action == ACTION_RELEASED:
|
||||||
self.add_dot()
|
self.add_dot() # ajout du point si possible
|
||||||
|
|
||||||
|
|
||||||
def show_dot(self, rgb=None):
|
def show_dot(self, rgb=None):
|
||||||
|
# affichage d'un point sur la matrice LED
|
||||||
rgb = rgb or [150, 150, 150]
|
rgb = rgb or [150, 150, 150]
|
||||||
self.sense.clear()
|
self.sense.clear()
|
||||||
for x in range(3, 5):
|
for x in range(3, 5):
|
||||||
for y in range(3, 5):
|
for y in range(3, 5):
|
||||||
self.sense.set_pixel(x, y, rgb)
|
self.sense.set_pixel(x, y, rgb)
|
||||||
# time.sleep(duration)
|
|
||||||
|
|
||||||
def show_dash(self, rgb=None):
|
def show_dash(self, rgb=None):
|
||||||
|
# affichage d'un trait sur la matrice LED
|
||||||
rgb = rgb or [150, 150, 150]
|
rgb = rgb or [150, 150, 150]
|
||||||
self.sense.clear()
|
self.sense.clear()
|
||||||
for x in range(1, 7):
|
for x in range(1, 7):
|
||||||
for y in range(3, 5):
|
for y in range(3, 5):
|
||||||
self.sense.set_pixel(x, y, rgb)
|
self.sense.set_pixel(x, y, rgb)
|
||||||
# time.sleep(duration)
|
|
||||||
|
|
||||||
def refresh(self, event):
|
def refresh(self, event):
|
||||||
pass
|
pass
|
||||||
# self.sense.clear()
|
# self.sense.clear()
|
||||||
|
|
||||||
def show_char(self, char, rgb=None):
|
def show_char(self, char, rgb=None):
|
||||||
""" affiche un caractère """
|
""" affiche un caractère dans la couleur définie (gris par defaut)"""
|
||||||
rgb = rgb or [150, 150, 150]
|
rgb = rgb or [150, 150, 150]
|
||||||
self.sense.clear()
|
self.sense.clear()
|
||||||
self.sense.show_letter(char, rgb)
|
self.sense.show_letter(char, rgb)
|
||||||
|
|
||||||
def add_dot(self):
|
def add_dot(self):
|
||||||
""" ajout d'un point au caractère courant """
|
""" ajout d'un point au caractère courant
|
||||||
|
tant que le caractère courant reste interpretable
|
||||||
|
sinon, affichage d'une erreur et reinitialise le caractère courant """
|
||||||
try:
|
try:
|
||||||
self.current_morse_char += '.'
|
self.current_morse_char += '.'
|
||||||
char = morse_talk.decode(self.current_morse_char)
|
char = morse_talk.decode(self.current_morse_char)
|
||||||
@ -113,7 +112,9 @@ class MorseSenseHatKeyer:
|
|||||||
self.current_morse_char = ''
|
self.current_morse_char = ''
|
||||||
|
|
||||||
def add_dash(self):
|
def add_dash(self):
|
||||||
""" ajout d'un trait au caractère courant """
|
""" ajout d'un trait au caractère courant
|
||||||
|
tant que le caractère courant reste interpretable
|
||||||
|
sinon, affichage d'une erreur et reinitialise le caractère courant """
|
||||||
self.current_morse_char += '-'
|
self.current_morse_char += '-'
|
||||||
try:
|
try:
|
||||||
char = morse_talk.decode(self.current_morse_char)
|
char = morse_talk.decode(self.current_morse_char)
|
||||||
@ -122,13 +123,15 @@ class MorseSenseHatKeyer:
|
|||||||
self.show_char('X', self.red)
|
self.show_char('X', self.red)
|
||||||
self.current_morse_char = ''
|
self.current_morse_char = ''
|
||||||
|
|
||||||
|
def show_error(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def reset_char(self):
|
def reset_char(self):
|
||||||
""" annule le caractère courant """
|
""" annule le caractère courant """
|
||||||
pass
|
self.current_morse_char = ''
|
||||||
|
|
||||||
def validate_char(self):
|
def validate_char(self):
|
||||||
""" valide la lettre courante """
|
""" valide la lettre courante, et donne un retour visuel """
|
||||||
char = morse_talk.decode(self.current_morse_char)
|
char = morse_talk.decode(self.current_morse_char)
|
||||||
self.show_char(char, self.green)
|
self.show_char(char, self.green)
|
||||||
self.message_ascii += char
|
self.message_ascii += char
|
||||||
@ -138,13 +141,13 @@ class MorseSenseHatKeyer:
|
|||||||
""" affiche le message courant """
|
""" affiche le message courant """
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # lors de l'execution du script en standalone
|
||||||
try:
|
try:
|
||||||
keyer = MorseSenseHatKeyer()
|
keyer = MorseSenseHatKeyer()
|
||||||
keyer.sense.show_message("morse://", 0.02)
|
keyer.sense.show_message("morse://", 0.02)
|
||||||
# waiting for jostick events and directing them to keyer
|
# waiting for joystick events and directing them to keyer
|
||||||
pause()
|
pause()
|
||||||
except (KeyboardInterrupt, SystemExit) as err:
|
except (KeyboardInterrupt, SystemExit) as err: # lors d'un CTRL+C pour interruption
|
||||||
sense = SenseHat()
|
sense = SenseHat()
|
||||||
sense.show_message('out', 0.02)
|
sense.show_message('bye', 0.02)
|
||||||
sense.clear()
|
sense.clear()
|