Merge pull request #1032 from pimoroni/inky-patches

Patches for Inky on SDK 2.1.0 + MicroPython edge
pull/1033/head
Philip Howard 2024-12-03 12:55:31 +00:00 zatwierdzone przez GitHub
commit 1991fdde2c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
62 zmienionych plików z 10 dodań i 3805 usunięć

Wyświetl plik

@ -23,8 +23,12 @@ jobs:
env:
PICO_SDK_PATH: $GITHUB_WORKSPACE/pico-sdk
steps:
- name: Compiler Cache Fixup
run: |
mkdir -p /home/runner/.ccache
- name: Compiler Cache
uses: actions/cache@v4
with:

Wyświetl plik

@ -42,8 +42,6 @@ jobs:
board: RPI_PICO_W
- name: stellar_unicorn
board: RPI_PICO_W
- name: inky_frame
board: PICO_W_INKY
env:
# MicroPython version will be contained in github.event.release.tag_name for releases

Wyświetl plik

@ -29,7 +29,7 @@ function micropython_clone {
function micropython_build_mpy_cross {
cd micropython/mpy-cross
ccache --zero-stats || true
CROSS_COMPILE="ccache " make
CROSS_COMPILE="ccache " USER_C_MODULES= make
ccache --show-stats || true
cd ../../
}

Wyświetl plik

@ -1,22 +0,0 @@
{
"deploy": [
"../deploy.md"
],
"docs": "",
"features": [
"Breadboard friendly",
"Castellated Pads",
"Micro USB",
"WiFi",
"Bluetooth"
],
"id": "rp2-pico-w",
"images": [
"rp2-pico-w.jpg"
],
"mcu": "rp2040",
"product": "Pico W",
"thumbnail": "",
"url": "https://www.raspberrypi.com/products/raspberry-pi-pico/",
"vendor": "Raspberry Pi"
}

Wyświetl plik

@ -1,15 +0,0 @@
include("$(PORT_DIR)/boards/manifest.py")
# https://github.com/micropython/micropython-lib/blob/master/micropython/bundles/bundle-networking/manifest.py
require("bundle-networking")
require("urllib.urequest")
require("umqtt.simple")
# SD Card
require("sdcard")
# Bluetooth
require("aioble")
freeze("../../modules_py", "pimoroni.py")
freeze("../../modules_py", "boot.py")

Wyświetl plik

@ -1,17 +0,0 @@
# cmake file for Pimoroni Inky with Raspberry Pi Pico W
set(MICROPY_BOARD RPI_PICO_W)
set(PICO_BOARD "pico_w")
# The C malloc is needed by cyw43-driver Bluetooth and Pimoroni Pico modules
set(MICROPY_C_HEAP_SIZE 4096)
set(MICROPY_PY_LWIP ON)
set(MICROPY_PY_NETWORK_CYW43 ON)
# Bluetooth
set(MICROPY_PY_BLUETOOTH ON)
set(MICROPY_BLUETOOTH_BTSTACK ON)
set(MICROPY_PY_BLUETOOTH_CYW43 ON)
# Board specific version of the frozen manifest
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)

Wyświetl plik

@ -1,23 +0,0 @@
// Board and hardware specific configuration
#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico W"
// todo: We need something to check our binary size
#define MICROPY_HW_FLASH_STORAGE_BYTES (848 * 1024)
// Enable networking.
#define MICROPY_PY_NETWORK 1
#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "InkyW"
// CYW43 driver configuration.
#define CYW43_USE_SPI (1)
#define CYW43_LWIP (1)
#define CYW43_GPIO (1)
#define CYW43_SPI_PIO (1)
// For debugging mbedtls - also set
// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose
// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1
#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT
#define MICROPY_HW_PIN_RESERVED(i) ((i) == CYW43_PIN_WL_HOST_WAKE || (i) == CYW43_PIN_WL_REG_ON)

Wyświetl plik

@ -1,30 +0,0 @@
GP0,GPIO0
GP1,GPIO1
GP2,GPIO2
GP3,GPIO3
GP4,GPIO4
GP5,GPIO5
GP6,GPIO6
GP7,GPIO7
GP8,GPIO8
GP9,GPIO9
GP10,GPIO10
GP11,GPIO11
GP12,GPIO12
GP13,GPIO13
GP14,GPIO14
GP15,GPIO15
GP16,GPIO16
GP17,GPIO17
GP18,GPIO18
GP19,GPIO19
GP20,GPIO20
GP21,GPIO21
GP22,GPIO22
GP26,GPIO26
GP27,GPIO27
GP28,GPIO28
WL_GPIO0,EXT_GPIO0
WL_GPIO1,EXT_GPIO1
WL_GPIO2,EXT_GPIO2
LED,EXT_GPIO0
1 GP0 GPIO0
2 GP1 GPIO1
3 GP2 GPIO2
4 GP3 GPIO3
5 GP4 GPIO4
6 GP5 GPIO5
7 GP6 GPIO6
8 GP7 GPIO7
9 GP8 GPIO8
10 GP9 GPIO9
11 GP10 GPIO10
12 GP11 GPIO11
13 GP12 GPIO12
14 GP13 GPIO13
15 GP14 GPIO14
16 GP15 GPIO15
17 GP16 GPIO16
18 GP17 GPIO17
19 GP18 GPIO18
20 GP19 GPIO19
21 GP20 GPIO20
22 GP21 GPIO21
23 GP22 GPIO22
24 GP26 GPIO26
25 GP27 GPIO27
26 GP28 GPIO28
27 WL_GPIO0 EXT_GPIO0
28 WL_GPIO1 EXT_GPIO1
29 WL_GPIO2 EXT_GPIO2
30 LED EXT_GPIO0

Wyświetl plik

@ -1,110 +1,3 @@
# Pico Inky Frame MicroPython Examples <!-- omit in toc -->
- [PicoGraphics](#picographics)
- [Examples](#examples)
- [Button Test](#button-test)
- [Carbon Intensity](#carbon-intensity)
- [Daily Activity](#daily-activity)
- [Dithering](#dithering)
- [Image Gallery](#image-gallery)
- [LED PWM](#led-pwm)
- [News](#news)
- [PlaceKitten](#placekitten)
- [Quote of the Day](#quote-of-the-day)
- [Random Joke](#random-joke)
- [RTC Demo](#rtc-demo)
- [SD Card Test](#sd-card-test)
- [XKCD Daily](#xkcd-daily)
## PicoGraphics
You can draw on Inky Frame using our tiny PicoGraphics display library.
- [PicoGraphics MicroPython function reference](../../modules/picographics)
## Examples
The wireless examples need `network_manager.py` and `WIFI_CONFIG.py` from the `common` directory to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
You'll also need to install the `micropython-urllib.urequest` library using Thonny's 'Tools' > 'Manage Packages' or `common/lib/urllib` which contains a compiled `.mpy` version that uses less RAM. You should place this directory in `lib` on your Pico W.
### Button Test
[button_test.py](button_test.py)
Inky Frame's buttons (and the RTC alarm, busy signal from the screen and external trigger from the hack header) are connected to a shift register to help conserve pins, and to allow these inputs to wake the board up from sleep.
This example demonstrates a simple way of reading when a button has been pushed by reading the shift register and checking if the bit in a specific position is 0 or 1.
### Carbon Intensity
[carbon_intensity.py](carbon_intensity.py)
This example connects to the Carbon Intensity API to give you a regional forecast of how your (UK) electricity is being generated and its carbon impact.
Find out more at https://carbonintensity.org.uk/
### Daily Activity
[inky_frame_daily_activity.py](inky_frame_daily_activity.py)
Generate a random activity from Bored API.
### Dithering
[inky_frame_dithering.py](inky_frame_dithering.py)
A basic example showing automatic dithering in action, as PicoGraphics tries to use Inky Frame's limited colour palette to match arbitrary colours.
### Image Gallery
[/image_gallery](../inky_frame/image_gallery)
This photo frame example displays local images on Inky Frame and lets you switch between them with the buttons. Use `image_gallery.py` if your images are stored on your Pico, or `image_gallery_sd.py` if the images are on your SD card.
### LED PWM
[led_pwm.py](led_pwm.py)
A basic example showing how you can control the brightness of Inky Frame's LEDs using PWM.
### News
[inky_frame_news.py](inky_frame_news.py)
Display headlines from BBC News.
### PlaceKitten
[inky_frame_placekitten.py](inky_frame_placekitten.py)
Download a random (from a small subset) image from PlaceKitten.
### Quote of the Day
[inky_frame_quote_of_the_day.py](inky_frame_quote_of_the_day.py)
Load the WikiQuotes Quote of the Day and display it.
### Random Joke
[inky_frame_random_joke.py](inky_frame_random_joke.py)
Load a random joke from JokeAPI.dev and display it.
Jokes are rendered into images "offline" by our feed2image service for two reasons:
1. Saves the Pico W having to process them
2. JokeAPI.dev needs TLS1.3 which Pico W does not support!
For bugs/contributions or to complain about a joke, see: https://github.com/pimoroni/feed2image
### RTC Demo
[inky_frame_rtc_demo.py](inky_frame_rtc_demo.py)
A basic example that sets the time/date from an NTP server, syncs the Inky and Pico RTCs and and makes Inky Frame wake up on a timer.
### SD Card Test
[sd_test.py](sd_test.py)
This simple example shows how to read and write from the SD card on Inky Frame.
### XKCD Daily
[inky_frame_xkcd_daily.py](inky_frame_xkcd_daily.py)
Download and display the daily webcomic from https://xkcd.com/
The webcomic is rendered "offline" by our feed2image service since xkcd.com requires TLS1.3!
For bugs/contributions see: https://github.com/pimoroni/feed2image
Moved to: [https://github.com/pimoroni/inky-frame/tree/main/examples](https://github.com/pimoroni/inky-frame/tree/main/examples)

Wyświetl plik

@ -1,86 +0,0 @@
# This example shows you a simple, non-interrupt way of reading Inky Frame's buttons with a loop that checks to see if buttons are pressed.
from pimoroni import ShiftRegister
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from machine import Pin
display = PicoGraphics(display=DISPLAY)
display.set_font("bitmap8")
# Inky Frame uses a shift register to read the buttons
SR_CLOCK = 8
SR_LATCH = 9
SR_OUT = 10
sr = ShiftRegister(SR_CLOCK, SR_LATCH, SR_OUT)
# set up the button LEDs
button_a_led = Pin(11, Pin.OUT)
button_b_led = Pin(12, Pin.OUT)
button_c_led = Pin(13, Pin.OUT)
button_d_led = Pin(14, Pin.OUT)
button_e_led = Pin(15, Pin.OUT)
# a handy function we can call to clear the screen
# display.set_pen(1) is white and display.set_pen(0) is black
def clear():
display.set_pen(1)
display.clear()
# set up
clear()
display.set_pen(0)
display.text("Press any button!", 10, 10, scale=4)
display.update()
while True:
button_a_led.off()
button_b_led.off()
button_c_led.off()
button_d_led.off()
button_e_led.off()
# read the shift register
# we can tell which button has been pressed by checking if a specific bit is 0 or 1
result = sr.read()
button_a = sr[7]
button_b = sr[6]
button_c = sr[5]
button_d = sr[4]
button_e = sr[3]
if button_a == 1: # if a button press is detected then...
button_a_led.on()
clear() # clear to white
display.set_pen(4) # change the pen colour
display.text("Button A pressed", 10, 10, scale=4) # display some text on the screen
display.update() # update the display
elif button_b == 1:
button_b_led.on()
clear()
display.set_pen(6)
display.text("Button B pressed", 10, 50, scale=4)
display.update()
elif button_c == 1:
button_c_led.on()
clear()
display.set_pen(5)
display.text("Button C pressed", 10, 90, scale=4)
display.update()
elif button_d == 1:
button_d_led.on()
clear()
display.set_pen(2)
display.text("Button D pressed", 10, 130, scale=4)
display.update()
elif button_e == 1:
button_e_led.on()
clear()
display.set_pen(3)
display.text("Button E pressed", 10, 170, scale=4)
display.update()

Wyświetl plik

@ -1,59 +0,0 @@
# This example allows you to test Inky Frame's buttons
# It does not update the screen.
from pimoroni import ShiftRegister
from machine import Pin
import time
# Inky Frame uses a shift register to read the buttons
SR_CLOCK = 8
SR_LATCH = 9
SR_OUT = 10
sr = ShiftRegister(SR_CLOCK, SR_LATCH, SR_OUT)
# Simple class to debounce button input and handle LED
class Button:
def __init__(self, idx, led, debounce=50):
self.led = Pin(led, Pin.OUT) # LEDs are just regular IOs
self.led.on()
self._idx = idx
self._debounce_time = debounce
self._changed = time.ticks_ms()
self._last_value = None
def debounced(self):
return time.ticks_ms() - self._changed > self._debounce_time
def get(self, sr):
value = sr[self._idx]
if value != self._last_value and self.debounced():
self._last_value = value
self._changed = time.ticks_ms()
return value
button_a = Button(7, 11)
button_b = Button(6, 12)
button_c = Button(5, 13)
button_d = Button(4, 14)
button_e = Button(3, 15)
while True:
sr.read()
if button_a.get(sr):
button_a.led.toggle()
if button_b.get(sr):
button_b.led.toggle()
if button_c.get(sr):
button_c.led.toggle()
if button_d.get(sr):
button_d.led.toggle()
if button_e.get(sr):
button_e.led.toggle()
time.sleep(1.0 / 60) # Poll 60 times/second

Wyświetl plik

@ -1,149 +0,0 @@
"""
This example connects to the Carbon Intensity API to give you a regional
forecast of how your (UK) electricity is being generated and its carbon impact.
Carbon Intensity API only reports data from the UK National Grid.
Find out more about what the numbers mean at:
https://carbonintensity.org.uk/
Make sure to uncomment the correct size for your display!
"""
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
import urequests
import inky_frame
import uasyncio
from network_manager import NetworkManager
import WIFI_CONFIG
# Set (the first half) of your UK postcode here
POSTCODE = "S9"
URL = "https://api.carbonintensity.org.uk/regional/postcode/" + str(POSTCODE)
def get_data():
global region, forecast, index, power_list, datetime_to, datetime_from
print(f"Requesting URL: {URL}")
r = urequests.get(URL)
# open the json data
j = r.json()
print("Data obtained!")
print(j)
# parse the json data
region = j["data"][0]["shortname"]
forecast = j["data"][0]["data"][0]["intensity"]["forecast"]
index = j["data"][0]["data"][0]["intensity"]["index"]
power_list = []
for power in j["data"][0]["data"][0]["generationmix"]:
power_list.append(power['perc'])
datetime_to = j["data"][0]["data"][0]["to"].split("T")
datetime_from = j["data"][0]["data"][0]["from"].split("T")
# close the socket
r.close()
def draw():
global graphics
# we're setting up our PicoGraphics buffer after we've made our RAM intensive https request
graphics = PicoGraphics(DISPLAY)
w, h = graphics.get_bounds()
graphics.set_pen(inky_frame.WHITE)
graphics.clear()
# draw lines
graphics.set_pen(inky_frame.BLACK)
graphics.line(0, int((h / 100) * 0), w, int((h / 100) * 0))
graphics.line(0, int((h / 100) * 50), w, int((h / 100) * 50))
graphics.set_font("bitmap8")
graphics.text('100%', w - 40, 10, scale=2)
graphics.text('50%', w - 40, int((h / 100) * 50 + 10), scale=2)
# draw bars
bar_colours = [
inky_frame.ORANGE,
inky_frame.RED,
inky_frame.ORANGE,
inky_frame.RED,
inky_frame.BLUE,
inky_frame.ORANGE,
inky_frame.GREEN,
inky_frame.GREEN,
inky_frame.GREEN
]
for p in power_list:
graphics.set_pen(bar_colours[power_list.index(p)])
graphics.rectangle(int(power_list.index(p) * w / 9), int(h - p * (h / 100)),
int(w / 9), int(h / 100 * p))
# draw labels
graphics.set_font('sans')
# once in white for a background
graphics.set_pen(inky_frame.WHITE)
labels = ['biomass', 'coal', 'imports', 'gas', 'nuclear', 'other', 'hydro', 'solar', 'wind']
graphics.set_thickness(4)
for label in labels:
graphics.text(f'{label}', int((labels.index(label) * w / 9) + (w / 9) / 2), h - 10, angle=270, scale=1)
# again in black
graphics.set_pen(inky_frame.BLACK)
labels = ['biomass', 'coal', 'imports', 'gas', 'nuclear', 'other', 'hydro', 'solar', 'wind']
graphics.set_thickness(2)
for label in labels:
graphics.text(f'{label}', int((labels.index(label) * w / 9) + (w / 9) / 2), h - 10, angle=270, scale=1)
# draw header
graphics.set_thickness(3)
graphics.set_pen(inky_frame.GREEN)
if index in ['high', 'very high']:
graphics.set_pen(inky_frame.RED)
if index in ['moderate']:
graphics.set_pen(inky_frame.ORANGE)
graphics.set_font("sans")
graphics.text('Carbon Intensity', 10, 35, scale=1.2, angle=0)
# draw small text
graphics.set_pen(inky_frame.BLACK)
graphics.set_font("bitmap8")
graphics.text(f'Region: {region}', int((w / 2) + 30), 10, scale=2)
graphics.text(f'{forecast} gCO2/kWh ({index})', int((w / 2) + 30), 30, scale=2)
graphics.text(f'{datetime_from[0]} {datetime_from[1]} to {datetime_to[1]}', int((w / 2) + 30), 50, scale=2)
graphics.update()
def status_handler(mode, status, ip):
print(mode, status, ip)
inky_frame.led_busy.on()
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
# connect to wifi
try:
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
except ImportError:
print("Add WIFI_CONFIG.py with your WiFi credentials")
get_data()
draw()
# Go to sleep if on battery power
inky_frame.turn_off()
# Or comment out the line above and uncomment this one to wake up and update every half hour
# inky_frame.sleep_for(30)
"""
Pico W RAM seems insufficient to decode a https request whilst having a PicoGraphics instance active.
If you are running off USB and want this to update periodically, you could incorporate a machine.reset()
to reset the Pico and start afresh every time.
"""

Wyświetl plik

@ -1,36 +0,0 @@
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
import pngdec
# Create a PicoGraphics instance
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
# Set the font
graphics.set_font("bitmap8")
# Create an instance of the PNG Decoder
png = pngdec.PNG(graphics)
# Clear the screen
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(0)
# Few lines of text.
graphics.text("PNG Pencil", 70, 100, WIDTH, 3)
# Open our PNG File from flash. In this example we're using a cartoon pencil.
# You can use Thonny to transfer PNG Images to your Inky Frame.
try:
png.open_file("pencil_256x256.png")
# Decode our PNG file and set the X and Y
png.decode(200, 100)
except OSError:
graphics.text("Unable to find PNG file! Copy 'pencil_256x256.png' to your Inky Frame using Thonny :)", 10, 70, WIDTH, 3)
# Start the screen update
graphics.update()

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 98 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 70 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 78 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 105 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 92 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 90 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 62 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 71 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 95 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 83 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 126 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 83 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 103 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 149 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 142 KiB

Wyświetl plik

@ -1,45 +0,0 @@
# Image Gallery <!-- omit in toc -->
- [Image transfer instructions](#image-transfer-instructions)
- [image\_gallery.py](#image_gallerypy)
- [image\_gallery\_sd.py / image\_gallery\_sd\_random.py](#image_gallery_sdpy--image_gallery_sd_randompy)
- [Image Credits](#image-credits)
Some example programs to display images on your Inky Frame, plus sample images in different sizes.
Use:
640x400 for Inky Frame 4.0"
600x448 for Inky Frame 5.7"
800x480 for Inky Frame 7.3"
If you want to use your own images, they will need to be the correct dimensions for your screen and saved *without progressive encoding*.
## Image transfer instructions
In all cases the images will need to be copied to the root of your Pico W or SD card.
### image_gallery.py
Copy the images to your Pico W using Thonny.
### image_gallery_sd.py / image_gallery_sd_random.py
Pop an SD card into your computer to copy the images across.
Alternatively, you can transfer them using Thonny, but you will have to mount the SD card using the REPL first:
```python
import os
import sdcard
from machine import Pin, SPI
sd_spi = SPI(0, sck=Pin(18, Pin.OUT), mosi=Pin(19, Pin.OUT), miso=Pin(16, Pin.OUT))
sd = sdcard.SDCard(sd_spi, Pin(22))
os.mount(sd, "/sd")
```
## Image Credits
Sample images from the Webb Space Telescope (credit: NASA, ESA, CSA, and STScI).
Find more gorgeous images and info @ https://webbtelescope.org/
... and Raspberry Pi <3

Wyświetl plik

@ -1,75 +0,0 @@
"""
An offline image gallery that switches between five jpg images.
Copy images into the root of your Pico's flash using Thonny.
If you want to use your own images they must be the screen dimensions
(or smaller) and saved as *non-progressive* jpgs.
Make sure to uncomment the correct size for your display!
"""
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
import inky_frame
import jpegdec
# you can change your file names here
IMAGE_A = "jwst1.jpg"
IMAGE_B = "jwst2.jpg"
IMAGE_C = "jwst3.jpg"
IMAGE_D = "jwst4.jpg"
IMAGE_E = "jwst5.jpg"
# set up the display
graphics = PicoGraphics(DISPLAY)
# Create a new JPEG decoder for our PicoGraphics
j = jpegdec.JPEG(graphics)
def display_image(filename):
# Open the JPEG file
j.open_file(filename)
# Decode the JPEG
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
# Display the result
graphics.update()
print('Press a button to display an image!')
while True:
inky_frame.button_a.led_off()
inky_frame.button_b.led_off()
inky_frame.button_c.led_off()
inky_frame.button_d.led_off()
inky_frame.button_e.led_off()
if inky_frame.button_a.read():
print('Refreshing image A.')
inky_frame.button_a.led_on()
display_image(IMAGE_A)
elif inky_frame.button_b.read():
print('Refreshing image B.')
inky_frame.button_b.led_on()
display_image(IMAGE_B)
elif inky_frame.button_c.read():
print('Refreshing image C.')
inky_frame.button_c.led_on()
display_image(IMAGE_C)
elif inky_frame.button_d.read():
print('Refreshing image D.')
inky_frame.button_d.led_on()
display_image(IMAGE_D)
elif inky_frame.button_e.read():
print('Refreshing image E.')
inky_frame.button_e.led_on()
display_image(IMAGE_E)
# Go to sleep if on battery power
inky_frame.turn_off()

Wyświetl plik

@ -1,84 +0,0 @@
"""
An offline image gallery that switches between five jpg images on your SD card.
Copy images to the root of your SD card by plugging it into a computer.
If you want to use your own images they must be the screen dimensions
(or smaller) and saved as *non-progressive* jpgs.
Make sure to uncomment the correct size for your display!
"""
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from machine import Pin, SPI
import jpegdec
import sdcard
import os
import inky_frame
# you can change your file names here
IMAGE_A = "sd/jwst1.jpg"
IMAGE_B = "sd/jwst2.jpg"
IMAGE_C = "sd/jwst3.jpg"
IMAGE_D = "sd/jwst4.jpg"
IMAGE_E = "sd/jwst5.jpg"
# set up the display
graphics = PicoGraphics(DISPLAY)
# set up the SD card
sd_spi = SPI(0, sck=Pin(18, Pin.OUT), mosi=Pin(19, Pin.OUT), miso=Pin(16, Pin.OUT))
sd = sdcard.SDCard(sd_spi, Pin(22))
os.mount(sd, "/sd")
# Create a new JPEG decoder for our PicoGraphics
j = jpegdec.JPEG(graphics)
def display_image(filename):
# Open the JPEG file
j.open_file(filename)
# Decode the JPEG
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
# Display the result
graphics.update()
# setup
print('Press a button to display an image!')
while True:
inky_frame.button_a.led_off()
inky_frame.button_b.led_off()
inky_frame.button_c.led_off()
inky_frame.button_d.led_off()
inky_frame.button_e.led_off()
if inky_frame.button_a.read():
print('Refreshing image A.')
inky_frame.button_a.led_on()
display_image(IMAGE_A)
elif inky_frame.button_b.read():
print('Refreshing image B.')
inky_frame.button_b.led_on()
display_image(IMAGE_B)
elif inky_frame.button_c.read():
print('Refreshing image C.')
inky_frame.button_c.led_on()
display_image(IMAGE_C)
elif inky_frame.button_d.read():
print('Refreshing image D.')
inky_frame.button_d.led_on()
display_image(IMAGE_D)
elif inky_frame.button_e.read():
print('Refreshing image E.')
inky_frame.button_e.led_on()
display_image(IMAGE_E)
# Go to sleep if on battery power
inky_frame.turn_off()

Wyświetl plik

@ -1,67 +0,0 @@
"""
An offline image gallery that displays a random image from your SD card
and updates on a timer.
Copy images to the root of your SD card by plugging it into a computer.
If you want to use your own images they must be the screen dimensions
(or smaller) and saved as *non-progressive* jpgs.
Make sure to uncomment the correct size for your display!
"""
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from machine import Pin, SPI
import jpegdec
import sdcard
import os
import inky_frame
import random
# how often to change image (in minutes)
UPDATE_INTERVAL = 60
# set up the display
graphics = PicoGraphics(DISPLAY)
# set up the SD card
sd_spi = SPI(0, sck=Pin(18, Pin.OUT), mosi=Pin(19, Pin.OUT), miso=Pin(16, Pin.OUT))
sd = sdcard.SDCard(sd_spi, Pin(22))
os.mount(sd, "/sd")
# Create a new JPEG decoder for our PicoGraphics
j = jpegdec.JPEG(graphics)
def display_image(filename):
# Open the JPEG file
j.open_file(filename)
# Decode the JPEG
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
# Display the result
graphics.update()
inky_frame.led_busy.on()
# Get a list of files that are in the directory
files = os.listdir("/sd")
# remove files from the list that aren't .jpgs or .jpegs
files = [f for f in files if f.endswith(".jpg") or f.endswith(".jpeg")]
while True:
# pick a random file
file = files[random.randrange(len(files))]
# Open the file
print(f"Displaying /sd/{file}")
display_image("/sd/" + file)
# Sleep or wait for a bit
print(f"Sleeping for {UPDATE_INTERVAL} minutes")
inky_frame.sleep_for(UPDATE_INTERVAL)

Wyświetl plik

@ -1,143 +0,0 @@
import gc
import WIFI_CONFIG
from network_manager import NetworkManager
import uasyncio
import ujson
from urllib import urequest
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from machine import Pin
from pimoroni_i2c import PimoroniI2C
from pcf85063a import PCF85063A
import time
I2C_SDA_PIN = 4
I2C_SCL_PIN = 5
HOLD_VSYS_EN_PIN = 2
# set up and enable vsys hold so we don't go to sleep
hold_vsys_en_pin = Pin(HOLD_VSYS_EN_PIN, Pin.OUT)
hold_vsys_en_pin.value(True)
# intialise the pcf85063a real time clock chip
i2c = PimoroniI2C(I2C_SDA_PIN, I2C_SCL_PIN, 100000)
rtc = PCF85063A(i2c)
# Length of time between updates in Seconds.
# Frequent updates will reduce battery life!
UPDATE_INTERVAL = 60 * 1
# API URL
URL = "https://www.boredapi.com/api/activity"
def status_handler(mode, status, ip):
print(mode, status, ip)
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
gc.collect()
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
gc.collect()
def display_quote(text, ox, oy, scale, wordwrap):
# Processing text is memory intensive
# so we'll do it one char at a time as we draw to the screen
line_height = 8 * scale
html = False
html_tag = ""
word = ""
space_width = graphics.measure_text(" ", scale=scale)
x = ox
y = oy
for char in text:
if char in "[]":
continue
if char == "<":
html = True
html_tag = ""
continue
if char == ">":
html = False
continue
if html:
if char in "/ ":
continue
html_tag += char
continue
if char in (" ", "\n") or html_tag == "br":
w = graphics.measure_text(word, scale=scale)
if x + w > wordwrap or char == "\n" or html_tag == "br":
x = ox
y += line_height
graphics.text(word, x, y, scale=scale)
word = ""
html_tag = ""
x += w + space_width
continue
word += char
# Last word
w = graphics.measure_text(word, scale=scale)
if x + w > wordwrap:
x = ox
y += line_height
graphics.text(word, x, y, scale=scale)
rtc.enable_timer_interrupt(True)
while True:
# Connect to WiFi
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
# Clear the screen
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(0)
# Grab the data
socket = urequest.urlopen(URL)
j = ujson.load(socket)
socket.close()
text = [j['activity'], j['type'], j['participants']]
# Page lines!
graphics.set_pen(3)
graphics.line(0, 65, WIDTH, 65)
for i in range(2, 13):
graphics.line(0, i * 35, WIDTH, i * 35)
# Page margin
graphics.set_pen(4)
graphics.line(50, 0, 50, HEIGHT)
graphics.set_pen(0)
# Main text
graphics.set_font("cursive")
graphics.set_pen(4)
graphics.set_font("cursive")
graphics.text("Activity Idea", 55, 30, WIDTH - 20, 2)
graphics.set_pen(0)
graphics.set_font("bitmap8")
display_quote(text[0], 55, 170, 5, WIDTH - 20)
graphics.set_pen(2)
graphics.text("Activity Type: " + text[1], 55, HEIGHT - 45, WIDTH - 20, 2)
graphics.text("Participants: " + str(text[2]), 400, HEIGHT - 45, WIDTH - 20, 2)
graphics.update()
# Time to have a little nap until the next update
rtc.set_timer(UPDATE_INTERVAL)
hold_vsys_en_pin.init(Pin.IN)
time.sleep(UPDATE_INTERVAL)

Wyświetl plik

@ -1,68 +0,0 @@
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_pen(1)
graphics.clear()
w = int(WIDTH / 8)
# Solid Colours
for p in range(8):
graphics.set_pen(p)
graphics.rectangle(w * p, 0, w, 50)
# "Greydient"
for x in range(WIDTH):
g = int(x / float(WIDTH) * 255)
graphics.set_pen(graphics.create_pen(g, g, g))
for y in range(30):
graphics.pixel(x, 60 + y)
# Rainbow Gradient
for x in range(WIDTH):
h = x / float(WIDTH)
graphics.set_pen(graphics.create_pen_hsv(h, 1.0, 1.0))
for y in range(100):
graphics.pixel(x, 100 + y)
# Block Colours & Text
graphics.set_pen(graphics.create_pen(128, 128, 0))
graphics.rectangle(0, 210, 200, 100)
graphics.set_pen(graphics.create_pen(200, 200, 200))
graphics.text("Hello", 10, 220)
graphics.text("Hello", 10, 240, scale=4.0)
graphics.set_pen(graphics.create_pen(0, 128, 128))
graphics.rectangle(200, 210, 200, 100)
graphics.set_pen(graphics.create_pen(200, 200, 200))
graphics.text("Hello", 210, 220)
graphics.text("Hello", 210, 240, scale=4.0)
graphics.set_pen(graphics.create_pen(128, 0, 128))
graphics.rectangle(400, 210, 200, 100)
graphics.set_pen(graphics.create_pen(200, 200, 200))
graphics.text("Hello", 410, 220)
graphics.text("Hello", 410, 240, scale=4.0)
# Red, Green and Blue gradients
for x in range(WIDTH):
g = int(x / float(WIDTH) * 255)
for y in range(20):
graphics.set_pen(graphics.create_pen(g, 0, 0))
graphics.pixel(x, 320 + y)
graphics.set_pen(graphics.create_pen(0, g, 0))
graphics.pixel(x, 350 + y)
graphics.set_pen(graphics.create_pen(0, 0, g))
graphics.pixel(x, 380 + y)
graphics.update()

Wyświetl plik

@ -1,192 +0,0 @@
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from network_manager import NetworkManager
import uasyncio
from urllib import urequest
import WIFI_CONFIG
import gc
import qrcode
from machine import Pin
from pimoroni_i2c import PimoroniI2C
from pcf85063a import PCF85063A
import time
I2C_SDA_PIN = 4
I2C_SCL_PIN = 5
HOLD_VSYS_EN_PIN = 2
# set up and enable vsys hold so we don't go to sleep
hold_vsys_en_pin = Pin(HOLD_VSYS_EN_PIN, Pin.OUT)
hold_vsys_en_pin.value(True)
# Uncomment one URL to use (Top Stories, World News and technology)
# URL = "http://feeds.bbci.co.uk/news/rss.xml"
# URL = "http://feeds.bbci.co.uk/news/world/rss.xml"
URL = "http://feeds.bbci.co.uk/news/technology/rss.xml"
# Length of time between updates in Seconds.
# Frequent updates will reduce battery life!
UPDATE_INTERVAL = 60 * 1
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_font("bitmap8")
code = qrcode.QRCode()
# intialise the pcf85063a real time clock chip
i2c = PimoroniI2C(I2C_SDA_PIN, I2C_SCL_PIN, 100000)
rtc = PCF85063A(i2c)
def status_handler(mode, status, ip):
print(mode, status, ip)
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
def read_until(stream, char):
result = b""
while True:
c = stream.read(1)
if c == char:
return result
result += c
def discard_until(stream, c):
while stream.read(1) != c:
pass
def parse_xml_stream(s, accept_tags, group_by, max_items=3):
tag = []
text = b""
count = 0
current = {}
while True:
char = s.read(1)
if len(char) == 0:
break
if char == b"<":
next_char = s.read(1)
# Discard stuff like <?xml vers...
if next_char == b"?":
discard_until(s, b">")
continue
# Detect <![CDATA
elif next_char == b"!":
s.read(1) # Discard [
discard_until(s, b"[") # Discard CDATA[
text = read_until(s, b"]")
discard_until(s, b">") # Discard ]>
gc.collect()
elif next_char == b"/":
current_tag = read_until(s, b">")
top_tag = tag[-1]
# Populate our result dict
if top_tag in accept_tags:
current[top_tag.decode("utf-8")] = text.decode("utf-8")
# If we've found a group of items, yield the dict
elif top_tag == group_by:
yield current
current = {}
count += 1
if count == max_items:
return
tag.pop()
text = b""
gc.collect()
continue
else:
current_tag = read_until(s, b">")
tag += [next_char + current_tag.split(b" ")[0]]
text = b""
gc.collect()
else:
text += char
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)
graphics.set_pen(1)
graphics.rectangle(ox, oy, size, size)
graphics.set_pen(0)
for x in range(size):
for y in range(size):
if code.get_module(x, y):
graphics.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size)
def get_rss():
try:
stream = urequest.urlopen(URL)
output = list(parse_xml_stream(stream, [b"title", b"description", b"guid", b"pubDate"], b"item"))
return output
except OSError as e:
print(e)
return False
rtc.enable_timer_interrupt(True)
while True:
# Connect to WiFi
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
# Gets Feed Data
feed = get_rss()
# Clear the screen
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(0)
# Title
graphics.text("Headlines from BBC News:", 10, 10, 300, 2)
# Draws 3 articles from the feed if they're available.
if feed:
graphics.set_pen(4)
graphics.text(feed[0]["title"], 10, 40, WIDTH - 150, 3 if graphics.measure_text(feed[0]["title"]) < 650 else 2)
graphics.text(feed[1]["title"], 130, 180, WIDTH - 140, 3 if graphics.measure_text(feed[1]["title"]) < 650 else 2)
graphics.text(feed[2]["title"], 10, 320, WIDTH - 150, 3 if graphics.measure_text(feed[2]["title"]) < 650 else 2)
graphics.set_pen(3)
graphics.text(feed[0]["description"], 10, 110 if graphics.measure_text(feed[0]["title"]) < 650 else 90, WIDTH - 150, 2)
graphics.text(feed[1]["description"], 130, 250 if graphics.measure_text(feed[1]["title"]) < 650 else 230, WIDTH - 145, 2)
graphics.text(feed[2]["description"], 10, 395 if graphics.measure_text(feed[2]["title"]) < 650 else 375, WIDTH - 150, 2)
code.set_text(feed[0]["guid"])
draw_qr_code(WIDTH - 110, 40, 100, code)
code.set_text(feed[1]["guid"])
draw_qr_code(10, 180, 100, code)
code.set_text(feed[2]["guid"])
draw_qr_code(WIDTH - 110, 320, 100, code)
else:
graphics.set_pen(4)
graphics.text("Error: Unable to get feed :(", 10, 40, WIDTH - 150, 4)
graphics.update()
# Time to have a little nap until the next update
rtc.set_timer(UPDATE_INTERVAL)
hold_vsys_en_pin.init(Pin.IN)
time.sleep(UPDATE_INTERVAL)

Wyświetl plik

@ -1,70 +0,0 @@
import gc
import uos
import random
import machine
import jpegdec
import uasyncio
import sdcard
import WIFI_CONFIG
from urllib import urequest
from network_manager import NetworkManager
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
"""
random placekitten (from a very small set)
You *must* insert an SD card into Inky Frame!
We need somewhere to save the jpg for display.
"""
gc.collect() # We're really gonna need that RAM!
def status_handler(mode, status, ip):
print(mode, status, ip)
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
FILENAME = "/sd/placekitten.jpg"
ENDPOINT = "http://placecats.com/{0}/{1}"
sd_spi = machine.SPI(0, sck=machine.Pin(18, machine.Pin.OUT), mosi=machine.Pin(19, machine.Pin.OUT), miso=machine.Pin(16, machine.Pin.OUT))
sd = sdcard.SDCard(sd_spi, machine.Pin(22))
uos.mount(sd, "/sd")
gc.collect() # Claw back some RAM!
url = ENDPOINT.format(WIDTH, HEIGHT + random.randint(0, 10))
socket = urequest.urlopen(url)
# Stream the image data from the socket onto disk in 1024 byte chunks
# the 600x448-ish jpeg will be roughly ~24k, we really don't have the RAM!
data = bytearray(1024)
with open(FILENAME, "wb") as f:
while True:
if socket.readinto(data) == 0:
break
f.write(data)
socket.close()
gc.collect() # We really are tight on RAM!
jpeg = jpegdec.JPEG(graphics)
gc.collect() # For good measure...
graphics.set_pen(1)
graphics.clear()
jpeg.open_file(FILENAME)
jpeg.decode()
graphics.update()

Wyświetl plik

@ -1,171 +0,0 @@
import gc
import time
import ujson
import uasyncio
import WIFI_CONFIG
from urllib import urequest
from network_manager import NetworkManager
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
ENDPOINT = "https://en.wikiquote.org/w/api.php?format=json&action=expandtemplates&prop=wikitext&text={{{{Wikiquote:Quote%20of%20the%20day/{3}%20{2},%20{0}}}}}"
MONTHNAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
last_date = ""
def parse_qotd(text):
print(text)
text = text.split("\n")
author = text[8].split("|")[2][5:-4]
text = text[6][2:]
gc.collect()
return text, author
def status_handler(mode, status, ip):
print(mode, status, ip)
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
gc.collect()
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_font("bitmap8")
gc.collect()
BADCHARS = {
"": "'",
"": "",
"": "..."
}
def display_quote(text, ox, oy, scale, wordwrap):
# Processing text is memory intensive
# so we'll do it one char at a time as we draw to the screen
line_height = 9 * scale
html_tag = ""
word = ""
extra_text = ""
space_width = graphics.measure_text(" ", scale=scale)
x = ox
y = oy
i = -1
while True:
if len(extra_text) == 0:
i += 1
if i >= len(text):
break
if len(extra_text) > 0:
char = extra_text[0]
extra_text = extra_text[1:]
else:
char = text[i]
if char in BADCHARS:
word += BADCHARS[char]
continue
# Unpick stuff like [[word]] and [[disambiguation|word]]
# and [[w:wikipedia_page|word]]
# test cases: July 8th 2022, July 12th 2022
if char == "[":
if text[i:i + 2] == "[[":
link = False
if text[i + 2:i + 4] == "w:":
link = True
i += 2
end = text[i:].index("]]")
if "|" in text[i + 2:i + end]:
parts = text[i + 2:i + end].split("|")
word = parts[1]
if not link:
extra_text = " (" + parts[0] + ")"
else:
word = text[i + 2:i + end]
i += end + 1
continue
if char == "&":
if text[i:i + 5] == "&amp;":
word += "&"
i += 4
continue
if char == "<":
j = i + text[i:].index(">")
html_tag = text[i + 1:j].replace("/", "").strip()
i = j
continue
if char in (" ", "\n") or html_tag == "br":
w = graphics.measure_text(word, scale=scale)
if x + w > wordwrap or char == "\n" or html_tag == "br":
x = ox
y += line_height
graphics.text(word, x, y, scale=scale)
word = ""
html_tag = ""
x += w + space_width
continue
word += char
# Last word
w = graphics.measure_text(word, scale=scale)
if x + w > wordwrap:
x = ox
y += line_height
graphics.text(word, x, y, scale=scale)
while True:
gc.collect()
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
date = list(time.localtime())[:3]
date.append(MONTHNAMES[date[1] - 1])
if "{3} {2}, {0}".format(*date) == last_date:
time.sleep(60)
continue
url = ENDPOINT.format(*date)
print("Requesting URL: {}".format(url))
socket = urequest.urlopen(url)
j = ujson.load(socket)
socket.close()
text = j['expandtemplates']['wikitext']
del j
gc.collect()
text, author = parse_qotd(text)
print(text)
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(0)
graphics.text("QoTD - {2} {3} {0:04d}".format(*date), 10, 10, scale=3)
display_quote(text, 10, 40, 2, wordwrap=WIDTH - 20)
graphics.text(author, 10, HEIGHT - 20, scale=2)
graphics.update()
gc.collect()
last_date = "{3} {2}, {0}".format(*date)
time.sleep(60)

Wyświetl plik

@ -1,90 +0,0 @@
import gc
import uos
import random
import machine
import jpegdec
import WIFI_CONFIG
import uasyncio
from network_manager import NetworkManager
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from urllib import urequest
gc.collect() # We're really gonna need that RAM!
def status_handler(mode, status, ip):
print(mode, status, ip)
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
FILENAME = "/sd/random-joke.jpg"
JOKE_IDS = "https://pimoroni.github.io/feed2image/jokeapi-ids.txt"
JOKE_IMG = "https://pimoroni.github.io/feed2image/jokeapi-{}-{}x{}.jpg"
import sdcard # noqa: E402 - putting this at the top causes an MBEDTLS OOM error!?
sd_spi = machine.SPI(0, sck=machine.Pin(18, machine.Pin.OUT), mosi=machine.Pin(19, machine.Pin.OUT), miso=machine.Pin(16, machine.Pin.OUT))
sd = sdcard.SDCard(sd_spi, machine.Pin(22))
uos.mount(sd, "/sd")
gc.collect() # Claw back some RAM!
# We don't have the RAM to store the list of Joke IDs in memory.
# the first line of `jokeapi-ids.txt` is a COUNT of IDs.
# Grab it, then pick a random line between 0 and COUNT.
# Seek to that line and ...y'know... there's our totally random joke ID
socket = urequest.urlopen(JOKE_IDS)
# Get the first line, which is a count of the joke IDs
number_of_lines = int(socket.readline().decode("ascii"))
print("Total jokes {}".format(number_of_lines))
# Pick a random joke (by its line number)
line = random.randint(0, number_of_lines)
print("Getting ID from line {}".format(line))
for x in range(line): # Throw away lines to get where we need
socket.readline()
# Read our chosen joke ID!
random_joke_id = int(socket.readline().decode("ascii"))
socket.close()
print("Random joke ID: {}".format(random_joke_id))
url = JOKE_IMG.format(random_joke_id, WIDTH, HEIGHT)
socket = urequest.urlopen(url)
# Stream the image data from the socket onto disk in 1024 byte chunks
# the 600x448-ish jpeg will be roughly ~24k, we really don't have the RAM!
data = bytearray(1024)
with open(FILENAME, "wb") as f:
while True:
if socket.readinto(data) == 0:
break
f.write(data)
socket.close()
del data
gc.collect() # We really are tight on RAM!
jpeg = jpegdec.JPEG(graphics)
gc.collect() # For good measure...
graphics.set_pen(1)
graphics.clear()
jpeg.open_file(FILENAME)
jpeg.decode()
graphics.update()

Wyświetl plik

@ -1,88 +0,0 @@
import time
import uasyncio
import WIFI_CONFIG
import inky_frame
from network_manager import NetworkManager
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
# Set tz_offset to be the number of hours off of UTC for your local zone.
# Examples: tz_offset = -7 # Pacific time (PST)
# tz_offset = 1 # CEST (Paris)
tz_offset = 0
tz_seconds = tz_offset * 3600
# Sync the Inky (always on) RTC to the Pico W so that "time.localtime()" works.
inky_frame.pcf_to_pico_rtc()
# Avoid running code unless we've been triggered by an event
# Keeps this example from locking up Thonny when we want to tweak the code
if inky_frame.woken_by_rtc() or inky_frame.woken_by_button():
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_pen(1)
graphics.clear()
# Look, just because this is an RTC demo,
# doesn't mean we can't make it rainbow.
for x in range(WIDTH):
h = x / WIDTH
p = graphics.create_pen_hsv(h, 1.0, 1.0)
graphics.set_pen(p)
graphics.line(x, 0, x, HEIGHT)
graphics.set_pen(0)
graphics.rectangle(0, 0, WIDTH, 14)
graphics.set_pen(1)
graphics.text("Inky Frame", 1, 0)
graphics.set_pen(0)
def status_handler(mode, status, ip):
print(mode, status, ip)
year, month, day, hour, minute, second, dow, _ = time.localtime(time.time() + tz_seconds)
# Connect to the network and get the time if it's not set
if year < 2023:
connected = False
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler, client_timeout=60)
t_start = time.time()
try:
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
connected = True
except RuntimeError:
pass
t_end = time.time()
if connected:
inky_frame.set_time()
graphics.text("Setting time from network...", 0, 40)
graphics.text(f"Connection took: {t_end-t_start}s", 0, 60)
else:
graphics.text("Failed to connect!", 0, 40)
# Display the date and time
year, month, day, hour, minute, second, dow, _ = time.localtime(time.time() + tz_seconds)
date_time = f"{year:04}/{month:02}/{day:02} {hour:02}:{minute:02}:{second:02}"
graphics.set_font("bitmap8")
text_scale = 8 if WIDTH == 800 else 6
text_height = 8 * text_scale
offset_left = (WIDTH - graphics.measure_text(date_time, scale=text_scale)) // 2
offset_top = (HEIGHT - text_height) // 2
graphics.set_pen(graphics.create_pen(50, 50, 50))
graphics.text(date_time, offset_left + 2, offset_top + 2, scale=text_scale)
graphics.set_pen(1)
graphics.text(date_time, offset_left, offset_top, scale=text_scale)
graphics.update()
inky_frame.sleep_for(2)

Wyświetl plik

@ -1,78 +0,0 @@
import gc
import uos
import machine
import jpegdec
import uasyncio
import sdcard
import WIFI_CONFIG
from urllib import urequest
from network_manager import NetworkManager
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
"""
xkcd daily
You *must* insert an SD card into Inky Frame!
We need somewhere to save the jpg for display.
Fetches a pre-processed XKCD daily image from:
https://pimoroni.github.io/feed2image/xkcd-daily.jpg
See https://xkcd.com/ for more webcomics!
"""
gc.collect() # We're really gonna need that RAM!
def status_handler(mode, status, ip):
print(mode, status, ip)
network_manager = NetworkManager("GB", status_handler=status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
FILENAME = "/sd/xkcd-daily.jpg"
ENDPOINT = "https://pimoroni.github.io/feed2image/xkcd-daily.jpg"
sd_spi = machine.SPI(0, sck=machine.Pin(18, machine.Pin.OUT), mosi=machine.Pin(19, machine.Pin.OUT), miso=machine.Pin(16, machine.Pin.OUT))
sd = sdcard.SDCard(sd_spi, machine.Pin(22))
uos.mount(sd, "/sd")
gc.collect() # Claw back some RAM!
url = ENDPOINT
if (WIDTH, HEIGHT) != (600, 448):
url = url.replace("xkcd-", f"xkcd-{WIDTH}x{HEIGHT}-")
socket = urequest.urlopen(url)
# Stream the image data from the socket onto disk in 1024 byte chunks
# the 600x448-ish jpeg will be roughly ~24k, we really don't have the RAM!
data = bytearray(1024)
with open(FILENAME, "wb") as f:
while True:
if socket.readinto(data) == 0:
break
f.write(data)
socket.close()
gc.collect() # We really are tight on RAM!
jpeg = jpegdec.JPEG(graphics)
gc.collect() # For good measure...
graphics.set_pen(1)
graphics.clear()
jpeg.open_file(FILENAME)
jpeg.decode()
graphics.update()

Wyświetl plik

@ -1,127 +0,0 @@
import gc
import ujson
from urllib import urequest
# Length of time between updates in Seconds.
# Frequent updates will reduce battery life!
UPDATE_INTERVAL = 240
# API URL
URL = "https://www.boredapi.com/api/activity"
graphics = None
text = None
gc.collect()
def display_quote(text, ox, oy, scale, wordwrap):
# Processing text is memory intensive
# so we'll do it one char at a time as we draw to the screen
line_height = 8 * scale
html = False
html_tag = ""
word = ""
space_width = graphics.measure_text(" ", scale=scale)
x = ox
y = oy
for char in text:
if char in "[]":
continue
if char == "<":
html = True
html_tag = ""
continue
if char == ">":
html = False
continue
if html:
if char in "/ ":
continue
html_tag += char
continue
if char in (" ", "\n") or html_tag == "br":
w = graphics.measure_text(word, scale=scale)
if x + w > wordwrap or char == "\n" or html_tag == "br":
x = ox
y += line_height
graphics.text(word, x, y, scale=scale)
word = ""
html_tag = ""
x += w + space_width
continue
word += char
# Last word
w = graphics.measure_text(word, scale=scale)
if x + w > wordwrap:
x = ox
y += line_height
graphics.text(word, x, y, scale=scale)
def update():
global text
gc.collect()
try:
# Grab the data
socket = urequest.urlopen(URL)
j = ujson.load(socket)
socket.close()
text = [j['activity'], j['type'], j['participants']]
gc.collect()
except OSError:
pass
def draw():
global text
WIDTH, HEIGHT = graphics.get_bounds()
# Clear the screen
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(0)
# Page lines!
graphics.set_pen(3)
graphics.line(0, 65, WIDTH, 65)
for i in range(2, 13):
graphics.line(0, i * 35, WIDTH, i * 35)
gc.collect()
# Page margin
graphics.set_pen(4)
graphics.line(50, 0, 50, HEIGHT)
graphics.set_pen(0)
# Main text
graphics.set_font("cursive")
graphics.set_pen(4)
graphics.set_font("cursive")
graphics.text("Activity Idea", 55, 30, WIDTH - 20, 2)
graphics.set_pen(0)
graphics.set_font("bitmap8")
if text:
display_quote(text[0], 55, 170, 5, WIDTH - 20)
gc.collect()
graphics.set_pen(2)
graphics.text("Activity Type: " + text[1], 55, HEIGHT - 45, WIDTH - 20, 2)
graphics.text("Participants: " + str(text[2]), 400, HEIGHT - 45, WIDTH - 20, 2)
else:
graphics.set_pen(4)
graphics.rectangle(0, (HEIGHT // 2) - 20, WIDTH, 40)
graphics.set_pen(1)
graphics.text("Unable to get activity data!", 5, (HEIGHT // 2) - 15, WIDTH, 2)
graphics.text("Check your network settings in secrets.py", 5, (HEIGHT // 2) + 2, WIDTH, 2)
graphics.update()
gc.collect()

Wyświetl plik

@ -1,154 +0,0 @@
from pimoroni_i2c import PimoroniI2C
from pcf85063a import PCF85063A
import math
from machine import Pin, PWM, Timer
import time
import inky_frame
import json
import network
import os
# Pin setup for VSYS_HOLD needed to sleep and wake.
HOLD_VSYS_EN_PIN = 2
hold_vsys_en_pin = Pin(HOLD_VSYS_EN_PIN, Pin.OUT)
# intialise the pcf85063a real time clock chip
I2C_SDA_PIN = 4
I2C_SCL_PIN = 5
i2c = PimoroniI2C(I2C_SDA_PIN, I2C_SCL_PIN, 100000)
rtc = PCF85063A(i2c)
led_warn = Pin(6, Pin.OUT)
# set up for the network LED
network_led_pwm = PWM(Pin(7))
network_led_pwm.freq(1000)
network_led_pwm.duty_u16(0)
# set the brightness of the network led
def network_led(brightness):
brightness = max(0, min(100, brightness)) # clamp to range
# gamma correct the brightness (gamma 2.8)
value = int(pow(brightness / 100.0, 2.8) * 65535.0 + 0.5)
network_led_pwm.duty_u16(value)
network_led_timer = Timer(-1)
network_led_pulse_speed_hz = 1
def network_led_callback(t):
# updates the network led brightness based on a sinusoid seeded by the current time
brightness = (math.sin(time.ticks_ms() * math.pi * 2 / (1000 / network_led_pulse_speed_hz)) * 40) + 60
value = int(pow(brightness / 100.0, 2.8) * 65535.0 + 0.5)
network_led_pwm.duty_u16(value)
# set the network led into pulsing mode
def pulse_network_led(speed_hz=1):
global network_led_timer, network_led_pulse_speed_hz
network_led_pulse_speed_hz = speed_hz
network_led_timer.deinit()
network_led_timer.init(period=50, mode=Timer.PERIODIC, callback=network_led_callback)
# turn off the network led and disable any pulsing animation that's running
def stop_network_led():
global network_led_timer
network_led_timer.deinit()
network_led_pwm.duty_u16(0)
def sleep(t):
# Time to have a little nap until the next update
rtc.clear_timer_flag()
rtc.set_timer(t, ttp=rtc.TIMER_TICK_1_OVER_60HZ)
rtc.enable_timer_interrupt(True)
# Set the HOLD VSYS pin to an input
# this allows the device to go into sleep mode when on battery power.
hold_vsys_en_pin.init(Pin.IN)
# Regular time.sleep for those powering from USB
time.sleep(60 * t)
# Turns off the button LEDs
def clear_button_leds():
inky_frame.button_a.led_off()
inky_frame.button_b.led_off()
inky_frame.button_c.led_off()
inky_frame.button_d.led_off()
inky_frame.button_e.led_off()
def network_connect(SSID, PSK):
# Enable the Wireless
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Number of attempts to make before timeout
max_wait = 10
# Sets the Wireless LED pulsing and attempts to connect to your local network.
pulse_network_led()
wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs
wlan.connect(SSID, PSK)
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(1)
stop_network_led()
network_led_pwm.duty_u16(30000)
# Handle connection error. Switches the Warn LED on.
if wlan.status() != 3:
stop_network_led()
led_warn.on()
state = {"run": None}
app = None
def file_exists(filename):
try:
return (os.stat(filename)[0] & 0x4000) == 0
except OSError:
return False
def clear_state():
if file_exists("state.json"):
os.remove("state.json")
def save_state(data):
with open("/state.json", "w") as f:
f.write(json.dumps(data))
f.flush()
def load_state():
global state
data = json.loads(open("/state.json", "r").read())
if type(data) is dict:
state = data
def update_state(running):
global state
state['run'] = running
save_state(state)
def launch_app(app_name):
global app
app = __import__(app_name)
print(app)
update_state(app_name)

Wyświetl plik

@ -1,662 +0,0 @@
"""
Tiny Web - pretty simple and powerful web server for tiny platforms like ESP8266 / ESP32
MIT license
(C) Konstantin Belyalov 2017-2018
"""
import logging
import uasyncio as asyncio
import uasyncio.core
import ujson as json
import gc
import uos as os
import sys
import uerrno as errno
import usocket as socket
log = logging.getLogger('WEB')
type_gen = type((lambda: (yield))()) # noqa: E275
# uasyncio v3 is shipped with MicroPython 1.13, and contains some subtle
# but breaking changes. See also https://github.com/peterhinch/micropython-async/blob/master/v3/README.md
IS_UASYNCIO_V3 = hasattr(asyncio, "__version__") and asyncio.__version__ >= (3,)
def urldecode_plus(s):
"""Decode urlencoded string (including '+' char).
Returns decoded string
"""
s = s.replace('+', ' ')
arr = s.split('%')
res = arr[0]
for it in arr[1:]:
if len(it) >= 2:
res += chr(int(it[:2], 16)) + it[2:]
elif len(it) == 0:
res += '%'
else:
res += it
return res
def parse_query_string(s):
"""Parse urlencoded string into dict.
Returns dict
"""
res = {}
pairs = s.split('&')
for p in pairs:
vals = [urldecode_plus(x) for x in p.split('=', 1)]
if len(vals) == 1:
res[vals[0]] = ''
else:
res[vals[0]] = vals[1]
return res
class HTTPException(Exception):
"""HTTP protocol exceptions"""
def __init__(self, code=400):
self.code = code
class request:
"""HTTP Request class"""
def __init__(self, _reader):
self.reader = _reader
self.headers = {}
self.method = b''
self.path = b''
self.query_string = b''
async def read_request_line(self):
"""Read and parse first line (AKA HTTP Request Line).
Function is generator.
Request line is something like:
GET /something/script?param1=val1 HTTP/1.1
"""
while True:
rl = await self.reader.readline()
# skip empty lines
if rl == b'\r\n' or rl == b'\n':
continue
break
rl_frags = rl.split()
if len(rl_frags) != 3:
raise HTTPException(400)
self.method = rl_frags[0]
url_frags = rl_frags[1].split(b'?', 1)
self.path = url_frags[0]
if len(url_frags) > 1:
self.query_string = url_frags[1]
async def read_headers(self, save_headers=[]):
"""Read and parse HTTP headers until \r\n\r\n:
Optional argument 'save_headers' controls which headers to save.
This is done mostly to deal with memory constrains.
Function is generator.
HTTP headers could be like:
Host: google.com
Content-Type: blah
\r\n
"""
while True:
gc.collect()
line = await self.reader.readline()
if line == b'\r\n':
break
frags = line.split(b':', 1)
if len(frags) != 2:
raise HTTPException(400)
if frags[0] in save_headers:
self.headers[frags[0]] = frags[1].strip()
async def read_parse_form_data(self):
"""Read HTTP form data (payload), if any.
Function is generator.
Returns:
- dict of key / value pairs
- None in case of no form data present
"""
# TODO: Probably there is better solution how to handle
# request body, at least for simple urlencoded forms - by processing
# chunks instead of accumulating payload.
gc.collect()
if b'Content-Length' not in self.headers:
return {}
# Parse payload depending on content type
if b'Content-Type' not in self.headers:
# Unknown content type, return unparsed, raw data
return {}
size = int(self.headers[b'Content-Length'])
if size > self.params['max_body_size'] or size < 0:
raise HTTPException(413)
data = await self.reader.readexactly(size)
# Use only string before ';', e.g:
# application/x-www-form-urlencoded; charset=UTF-8
ct = self.headers[b'Content-Type'].split(b';', 1)[0]
try:
if ct == b'application/json':
return json.loads(data)
elif ct == b'application/x-www-form-urlencoded':
return parse_query_string(data.decode())
except ValueError:
# Re-generate exception for malformed form data
raise HTTPException(400)
class response:
"""HTTP Response class"""
def __init__(self, _writer):
self.writer = _writer
self.send = _writer.awrite
self.code = 200
self.version = '1.0'
self.headers = {}
async def _send_headers(self):
"""Compose and send:
- HTTP request line
- HTTP headers following by \r\n.
This function is generator.
P.S.
Because of usually we have only a few HTTP headers (2-5) it doesn't make sense
to send them separately - sometimes it could increase latency.
So combining headers together and send them as single "packet".
"""
# Request line
hdrs = 'HTTP/{} {} MSG\r\n'.format(self.version, self.code)
# Headers
for k, v in self.headers.items():
hdrs += '{}: {}\r\n'.format(k, v)
hdrs += '\r\n'
# Collect garbage after small mallocs
gc.collect()
await self.send(hdrs)
async def error(self, code, msg=None):
"""Generate HTTP error response
This function is generator.
Arguments:
code - HTTP response code
Example:
# Not enough permissions. Send HTTP 403 - Forbidden
await resp.error(403)
"""
self.code = code
if msg:
self.add_header('Content-Length', len(msg))
await self._send_headers()
if msg:
await self.send(msg)
async def redirect(self, location, msg=None):
"""Generate HTTP redirect response to 'location'.
Basically it will generate HTTP 302 with 'Location' header
Arguments:
location - URL to redirect to
Example:
# Redirect to /something
await resp.redirect('/something')
"""
self.code = 302
self.add_header('Location', location)
if msg:
self.add_header('Content-Length', len(msg))
await self._send_headers()
if msg:
await self.send(msg)
def add_header(self, key, value):
"""Add HTTP response header
Arguments:
key - header name
value - header value
Example:
resp.add_header('Content-Encoding', 'gzip')
"""
self.headers[key] = value
def add_access_control_headers(self):
"""Add Access Control related HTTP response headers.
This is required when working with RestApi (JSON requests)
"""
self.add_header('Access-Control-Allow-Origin', self.params['allowed_access_control_origins'])
self.add_header('Access-Control-Allow-Methods', self.params['allowed_access_control_methods'])
self.add_header('Access-Control-Allow-Headers', self.params['allowed_access_control_headers'])
async def start_html(self):
"""Start response with HTML content type.
This function is generator.
Example:
await resp.start_html()
await resp.send('<html><h1>Hello, world!</h1></html>')
"""
self.add_header('Content-Type', 'text/html')
await self._send_headers()
async def send_file(self, filename, content_type=None, content_encoding=None, max_age=2592000, buf_size=128):
"""Send local file as HTTP response.
This function is generator.
Arguments:
filename - Name of file which exists in local filesystem
Keyword arguments:
content_type - Filetype. By default - None means auto-detect.
max_age - Cache control. How long browser can keep this file on disk.
By default - 30 days
Set to 0 - to disable caching.
Example 1: Default use case:
await resp.send_file('images/cat.jpg')
Example 2: Disable caching:
await resp.send_file('static/index.html', max_age=0)
Example 3: Override content type:
await resp.send_file('static/file.bin', content_type='application/octet-stream')
"""
try:
# Get file size
stat = os.stat(filename)
slen = str(stat[6])
self.add_header('Content-Length', slen)
# Find content type
if content_type:
self.add_header('Content-Type', content_type)
# Add content-encoding, if any
if content_encoding:
self.add_header('Content-Encoding', content_encoding)
# Since this is static content is totally make sense
# to tell browser to cache it, however, you can always
# override it by setting max_age to zero
self.add_header('Cache-Control', 'max-age={}, public'.format(max_age))
with open(filename) as f:
await self._send_headers()
gc.collect()
buf = bytearray(min(stat[6], buf_size))
while True:
size = f.readinto(buf)
if size == 0:
break
await self.send(buf, sz=size)
except OSError as e:
# special handling for ENOENT / EACCESS
if e.args[0] in (errno.ENOENT, errno.EACCES):
raise HTTPException(404)
else:
raise
async def restful_resource_handler(req, resp, param=None):
"""Handler for RESTful API endpoins"""
# Gather data - query string, JSON in request body...
data = await req.read_parse_form_data()
# Add parameters from URI query string as well
# This one is actually for simply development of RestAPI
if req.query_string != b'':
data.update(parse_query_string(req.query_string.decode()))
# Call actual handler
_handler, _kwargs = req.params['_callmap'][req.method]
# Collect garbage before / after handler execution
gc.collect()
if param:
res = _handler(data, param, **_kwargs)
else:
res = _handler(data, **_kwargs)
gc.collect()
# Handler result could be:
# 1. generator - in case of large payload
# 2. string - just string :)
# 2. dict - meaning client what tinyweb to convert it to JSON
# it can also return error code together with str / dict
# res = {'blah': 'blah'}
# res = {'blah': 'blah'}, 201
if isinstance(res, type_gen):
# Result is generator, use chunked response
# NOTICE: HTTP 1.0 by itself does not support chunked responses, so, making workaround:
# Response is HTTP/1.1 with Connection: close
resp.version = '1.1'
resp.add_header('Connection', 'close')
resp.add_header('Content-Type', 'application/json')
resp.add_header('Transfer-Encoding', 'chunked')
resp.add_access_control_headers()
await resp._send_headers()
# Drain generator
for chunk in res:
chunk_len = len(chunk.encode('utf-8'))
await resp.send('{:x}\r\n'.format(chunk_len))
await resp.send(chunk)
await resp.send('\r\n')
gc.collect()
await resp.send('0\r\n\r\n')
else:
if type(res) is tuple:
resp.code = res[1]
res = res[0]
elif res is None:
raise Exception('Result expected')
# Send response
if type(res) is dict:
res_str = json.dumps(res)
else:
res_str = res
resp.add_header('Content-Type', 'application/json')
resp.add_header('Content-Length', str(len(res_str)))
resp.add_access_control_headers()
await resp._send_headers()
await resp.send(res_str)
class webserver:
def __init__(self, request_timeout=3, max_concurrency=3, backlog=16, debug=False):
"""Tiny Web Server class.
Keyword arguments:
request_timeout - Time for client to send complete request
after that connection will be closed.
max_concurrency - How many connections can be processed concurrently.
It is very important to limit this number because of
memory constrain.
Default value depends on platform
backlog - Parameter to socket.listen() function. Defines size of
pending to be accepted connections queue.
Must be greater than max_concurrency
debug - Whether send exception info (text + backtrace)
to client together with HTTP 500 or not.
"""
self.loop = asyncio.get_event_loop()
self.request_timeout = request_timeout
self.max_concurrency = max_concurrency
self.backlog = backlog
self.debug = debug
self.explicit_url_map = {}
self.catch_all_handler = None
self.parameterized_url_map = {}
# Currently opened connections
self.conns = {}
# Statistics
self.processed_connections = 0
def _find_url_handler(self, req):
"""Helper to find URL handler.
Returns tuple of (function, opts, param) or (None, None) if not found.
"""
# First try - lookup in explicit (non parameterized URLs)
if req.path in self.explicit_url_map:
return self.explicit_url_map[req.path]
# Second try - strip last path segment and lookup in another map
idx = req.path.rfind(b'/') + 1
path2 = req.path[:idx]
if len(path2) > 0 and path2 in self.parameterized_url_map:
# Save parameter into request
req._param = req.path[idx:].decode()
return self.parameterized_url_map[path2]
if self.catch_all_handler:
return self.catch_all_handler
# No handler found
return (None, None)
async def _handle_request(self, req, resp):
await req.read_request_line()
# Find URL handler
req.handler, req.params = self._find_url_handler(req)
if not req.handler:
# No URL handler found - read response and issue HTTP 404
await req.read_headers()
raise HTTPException(404)
# req.params = params
# req.handler = han
resp.params = req.params
# Read / parse headers
await req.read_headers(req.params['save_headers'])
async def _handler(self, reader, writer):
"""Handler for TCP connection with
HTTP/1.0 protocol implementation
"""
gc.collect()
try:
req = request(reader)
resp = response(writer)
# Read HTTP Request with timeout
await asyncio.wait_for(self._handle_request(req, resp),
self.request_timeout)
# OPTIONS method is handled automatically
if req.method == b'OPTIONS':
resp.add_access_control_headers()
# Since we support only HTTP 1.0 - it is important
# to tell browser that there is no payload expected
# otherwise some webkit based browsers (Chrome)
# treat this behavior as an error
resp.add_header('Content-Length', '0')
await resp._send_headers()
return
# Ensure that HTTP method is allowed for this path
if req.method not in req.params['methods']:
raise HTTPException(405)
# Handle URL
gc.collect()
if hasattr(req, '_param'):
await req.handler(req, resp, req._param)
else:
await req.handler(req, resp)
# Done here
except (asyncio.CancelledError, asyncio.TimeoutError):
pass
except OSError as e:
# Do not send response for connection related errors - too late :)
# P.S. code 32 - is possible BROKEN PIPE error (TODO: is it true?)
if e.args[0] not in (errno.ECONNABORTED, errno.ECONNRESET, 32):
try:
await resp.error(500)
except Exception as e:
log.exc(e, "")
except HTTPException as e:
try:
await resp.error(e.code)
except Exception as e:
log.exc(e)
except Exception as e:
# Unhandled expection in user's method
log.error(req.path.decode())
log.exc(e, "")
try:
await resp.error(500)
# Send exception info if desired
if self.debug:
sys.print_exception(e, resp.writer.s)
except Exception:
pass
finally:
await writer.aclose()
# Max concurrency support -
# if queue is full schedule resume of TCP server task
if len(self.conns) == self.max_concurrency:
self.loop.create_task(self._server_coro)
# Delete connection, using socket as a key
del self.conns[id(writer.s)]
def add_route(self, url, f, **kwargs):
"""Add URL to function mapping.
Arguments:
url - url to map function with
f - function to map
Keyword arguments:
methods - list of allowed methods. Defaults to ['GET', 'POST']
save_headers - contains list of HTTP headers to be saved. Case sensitive. Default - empty.
max_body_size - Max HTTP body size (e.g. POST form data). Defaults to 1024
allowed_access_control_headers - Default value for the same name header. Defaults to *
allowed_access_control_origins - Default value for the same name header. Defaults to *
"""
if url == '' or '?' in url:
raise ValueError('Invalid URL')
# Initial params for route
params = {'methods': ['GET'],
'save_headers': [],
'max_body_size': 1024,
'allowed_access_control_headers': '*',
'allowed_access_control_origins': '*',
}
params.update(kwargs)
params['allowed_access_control_methods'] = ', '.join(params['methods'])
# Convert methods/headers to bytestring
params['methods'] = [x.encode() for x in params['methods']]
params['save_headers'] = [x.encode() for x in params['save_headers']]
# If URL has a parameter
if url.endswith('>'):
idx = url.rfind('<')
path = url[:idx]
idx += 1
param = url[idx:-1]
if path.encode() in self.parameterized_url_map:
raise ValueError('URL exists')
params['_param_name'] = param
self.parameterized_url_map[path.encode()] = (f, params)
if url.encode() in self.explicit_url_map:
raise ValueError('URL exists')
self.explicit_url_map[url.encode()] = (f, params)
def add_resource(self, cls, url, **kwargs):
"""Map resource (RestAPI) to URL
Arguments:
cls - Resource class to map to
url - url to map to class
kwargs - User defined key args to pass to the handler.
Example:
class myres():
def get(self, data):
return {'hello': 'world'}
app.add_resource(myres, '/api/myres')
"""
methods = []
callmap = {}
# Create instance of resource handler, if passed as just class (not instance)
try:
obj = cls()
except TypeError:
obj = cls
# Get all implemented HTTP methods and make callmap
for m in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']:
fn = m.lower()
if hasattr(obj, fn):
methods.append(m)
callmap[m.encode()] = (getattr(obj, fn), kwargs)
self.add_route(url, restful_resource_handler,
methods=methods,
save_headers=['Content-Length', 'Content-Type'],
_callmap=callmap)
def catchall(self):
"""Decorator for catchall()
Example:
@app.catchall()
def catchall_handler(req, resp):
response.code = 404
await response.start_html()
await response.send('<html><body><h1>My custom 404!</h1></html>\n')
"""
params = {'methods': [b'GET'], 'save_headers': [], 'max_body_size': 1024, 'allowed_access_control_headers': '*', 'allowed_access_control_origins': '*'}
def _route(f):
self.catch_all_handler = (f, params)
return f
return _route
def route(self, url, **kwargs):
"""Decorator for add_route()
Example:
@app.route('/')
def index(req, resp):
await resp.start_html()
await resp.send('<html><body><h1>Hello, world!</h1></html>\n')
"""
def _route(f):
self.add_route(url, f, **kwargs)
return f
return _route
def resource(self, url, method='GET', **kwargs):
"""Decorator for add_resource() method
Examples:
@app.resource('/users')
def users(data):
return {'a': 1}
@app.resource('/messages/<topic_id>')
async def index(data, topic_id):
yield '{'
yield '"topic_id": "{}",'.format(topic_id)
yield '"message": "test",'
yield '}'
"""
def _resource(f):
self.add_route(url, restful_resource_handler,
methods=[method],
save_headers=['Content-Length', 'Content-Type'],
_callmap={method.encode(): (f, kwargs)})
return f
return _resource
async def _tcp_server(self, host, port, backlog):
"""TCP Server implementation.
Opens socket for accepting connection and
creates task for every new accepted connection
"""
addr = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)[0][-1]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addr)
sock.listen(backlog)
try:
while True:
if IS_UASYNCIO_V3:
yield uasyncio.core._io_queue.queue_read(sock)
else:
yield asyncio.IORead(sock)
csock, caddr = sock.accept()
csock.setblocking(False)
# Start handler / keep it in the map - to be able to
# shutdown gracefully - by close all connections
self.processed_connections += 1
hid = id(csock)
handler = self._handler(asyncio.StreamReader(csock),
asyncio.StreamWriter(csock, {}))
self.conns[hid] = handler
self.loop.create_task(handler)
# In case of max concurrency reached - temporary pause server:
# 1. backlog must be greater than max_concurrency, otherwise
# client will got "Connection Reset"
# 2. Server task will be resumed whenever one active connection finished
if len(self.conns) == self.max_concurrency:
# Pause
yield False
except asyncio.CancelledError:
return
finally:
sock.close()
def run(self, host="127.0.0.1", port=8081, loop_forever=True):
"""Run Web Server. By default it runs forever.
Keyword arguments:
host - host to listen on. By default - localhost (127.0.0.1)
port - port to listen on. By default - 8081
loop_forever - run loo.loop_forever(), otherwise caller must run it by itself.
"""
self._server_coro = self._tcp_server(host, port, self.backlog)
self.loop.create_task(self._server_coro)
if loop_forever:
self.loop.run_forever()
def shutdown(self):
"""Gracefully shutdown Web Server"""
asyncio.cancel(self._server_coro)
for hid, coro in self.conns.items():
asyncio.cancel(coro)

Wyświetl plik

@ -1,168 +0,0 @@
import gc
import time
from machine import reset
import inky_helper as ih
# Uncomment the line for your Inky Frame display size
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
# Create a secrets.py with your Wifi details to be able to get the time
#
# secrets.py should contain:
# WIFI_SSID = "Your WiFi SSID"
# WIFI_PASSWORD = "Your WiFi password"
# A short delay to give USB chance to initialise
time.sleep(0.5)
# Setup for the display.
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_font("bitmap8")
def launcher():
# Apply an offset for the Inky Frame 5.7".
if HEIGHT == 448:
y_offset = 20
# Inky Frame 7.3"
elif HEIGHT == 480:
y_offset = 35
# Inky Frame 4"
else:
y_offset = 0
# Draws the menu
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(0)
graphics.set_pen(graphics.create_pen(255, 215, 0))
graphics.rectangle(0, 0, WIDTH, 50)
graphics.set_pen(0)
title = "Launcher"
title_len = graphics.measure_text(title, 4) // 2
graphics.text(title, (WIDTH // 2 - title_len), 10, WIDTH, 4)
graphics.set_pen(4)
graphics.rectangle(30, HEIGHT - (340 + y_offset), WIDTH - 100, 50)
graphics.set_pen(1)
graphics.text("A. NASA Picture of the Day", 35, HEIGHT - (325 + y_offset), 600, 3)
graphics.set_pen(6)
graphics.rectangle(30, HEIGHT - (280 + y_offset), WIDTH - 150, 50)
graphics.set_pen(1)
graphics.text("B. Word Clock", 35, HEIGHT - (265 + y_offset), 600, 3)
graphics.set_pen(2)
graphics.rectangle(30, HEIGHT - (220 + y_offset), WIDTH - 200, 50)
graphics.set_pen(1)
graphics.text("C. Daily Activity", 35, HEIGHT - (205 + y_offset), 600, 3)
graphics.set_pen(3)
graphics.rectangle(30, HEIGHT - (160 + y_offset), WIDTH - 250, 50)
graphics.set_pen(1)
graphics.text("D. Headlines", 35, HEIGHT - (145 + y_offset), 600, 3)
graphics.set_pen(0)
graphics.rectangle(30, HEIGHT - (100 + y_offset), WIDTH - 300, 50)
graphics.set_pen(1)
graphics.text("E. Random Joke", 35, HEIGHT - (85 + y_offset), 600, 3)
graphics.set_pen(graphics.create_pen(220, 220, 220))
graphics.rectangle(WIDTH - 100, HEIGHT - (340 + y_offset), 70, 50)
graphics.rectangle(WIDTH - 150, HEIGHT - (280 + y_offset), 120, 50)
graphics.rectangle(WIDTH - 200, HEIGHT - (220 + y_offset), 170, 50)
graphics.rectangle(WIDTH - 250, HEIGHT - (160 + y_offset), 220, 50)
graphics.rectangle(WIDTH - 300, HEIGHT - (100 + y_offset), 270, 50)
graphics.set_pen(0)
note = "Hold A + E, then press Reset, to return to the Launcher"
note_len = graphics.measure_text(note, 2) // 2
graphics.text(note, (WIDTH // 2 - note_len), HEIGHT - 30, 600, 2)
ih.led_warn.on()
graphics.update()
ih.led_warn.off()
# Now we've drawn the menu to the screen, we wait here for the user to select an app.
# Then once an app is selected, we set that as the current app and reset the device and load into it.
# You can replace any of the included examples with one of your own,
# just replace the name of the app in the line "ih.update_last_app("nasa_apod")"
while True:
if ih.inky_frame.button_a.read():
ih.inky_frame.button_a.led_on()
ih.update_state("nasa_apod")
time.sleep(0.5)
reset()
if ih.inky_frame.button_b.read():
ih.inky_frame.button_b.led_on()
ih.update_state("word_clock")
time.sleep(0.5)
reset()
if ih.inky_frame.button_c.read():
ih.inky_frame.button_c.led_on()
ih.update_state("daily_activity")
time.sleep(0.5)
reset()
if ih.inky_frame.button_d.read():
ih.inky_frame.button_d.led_on()
ih.update_state("news_headlines")
time.sleep(0.5)
reset()
if ih.inky_frame.button_e.read():
ih.inky_frame.button_e.led_on()
ih.update_state("random_joke")
time.sleep(0.5)
reset()
# Turn any LEDs off that may still be on from last run.
ih.clear_button_leds()
ih.led_warn.off()
if ih.inky_frame.button_a.read() and ih.inky_frame.button_e.read():
launcher()
ih.clear_button_leds()
if ih.file_exists("state.json"):
# Loads the JSON and launches the app
ih.load_state()
ih.launch_app(ih.state['run'])
# Passes the the graphics object from the launcher to the app
ih.app.graphics = graphics
ih.app.WIDTH = WIDTH
ih.app.HEIGHT = HEIGHT
else:
launcher()
try:
from secrets import WIFI_SSID, WIFI_PASSWORD
ih.network_connect(WIFI_SSID, WIFI_PASSWORD)
except ImportError:
print("Create secrets.py with your WiFi credentials")
# Get some memory back, we really need it!
gc.collect()
# The main loop executes the update and draw function from the imported app,
# and then goes to sleep ZzzzZZz
file = ih.file_exists("state.json")
print(file)
while True:
ih.app.update()
ih.led_warn.on()
ih.app.draw()
ih.led_warn.off()
ih.sleep(ih.app.UPDATE_INTERVAL)

Wyświetl plik

@ -1,101 +0,0 @@
import gc
import jpegdec
from urllib import urequest
from ujson import load
gc.collect()
graphics = None
WIDTH = None
HEIGHT = None
FILENAME = "nasa-apod-daily"
# A Demo Key is used in this example and is IP rate limited. You can get your own API Key from https://api.nasa.gov/
API_URL = "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"
# Length of time between updates in minutes.
# Frequent updates will reduce battery life!
UPDATE_INTERVAL = 240
# Variable for storing the NASA APOD Title
apod_title = None
def show_error(text):
graphics.set_pen(4)
graphics.rectangle(0, 10, WIDTH, 35)
graphics.set_pen(1)
graphics.text(text, 5, 16, 400, 2)
def update():
global apod_title
if HEIGHT == 448:
# Image for Inky Frame 5.7
IMG_URL = "https://pimoroni.github.io/feed2image/nasa-apod-daily.jpg"
elif HEIGHT == 400:
# Image for Inky Frame 4.0
IMG_URL = "https://pimoroni.github.io/feed2image/nasa-apod-640x400-daily.jpg"
elif HEIGHT == 480:
# Image for Inky Frame 7.3
IMG_URL = "https://pimoroni.github.io/feed2image/nasa-apod-800x480-daily.jpg"
try:
# Grab the data
socket = urequest.urlopen(API_URL)
gc.collect()
j = load(socket)
socket.close()
apod_title = j['title']
gc.collect()
except OSError as e:
print(e)
apod_title = "Image Title Unavailable"
try:
# Grab the image
socket = urequest.urlopen(IMG_URL)
gc.collect()
data = bytearray(1024)
with open(FILENAME, "wb") as f:
while True:
if socket.readinto(data) == 0:
break
f.write(data)
socket.close()
del data
gc.collect()
except OSError as e:
print(e)
show_error("Unable to download image")
def draw():
jpeg = jpegdec.JPEG(graphics)
gc.collect() # For good measure...
graphics.set_pen(1)
graphics.clear()
try:
jpeg.open_file(FILENAME)
jpeg.decode()
except OSError:
graphics.set_pen(4)
graphics.rectangle(0, (HEIGHT // 2) - 20, WIDTH, 40)
graphics.set_pen(1)
graphics.text("Unable to display image!", 5, (HEIGHT // 2) - 15, WIDTH, 2)
graphics.text("Check your network settings in secrets.py", 5, (HEIGHT // 2) + 2, WIDTH, 2)
graphics.set_pen(0)
graphics.rectangle(0, HEIGHT - 25, WIDTH, 25)
graphics.set_pen(1)
graphics.text(apod_title, 5, HEIGHT - 20, WIDTH, 2)
gc.collect()
graphics.update()

Wyświetl plik

@ -1,170 +0,0 @@
from urllib import urequest
import gc
import qrcode
# Uncomment one URL to use (Top Stories, World News and technology)
# URL = "https://feeds.bbci.co.uk/news/rss.xml"
# URL = "https://feeds.bbci.co.uk/news/world/rss.xml"
URL = "https://feeds.bbci.co.uk/news/technology/rss.xml"
# Length of time between updates in minutes.
# Frequent updates will reduce battery life!
UPDATE_INTERVAL = 90
graphics = None
WIDTH = None
HEIGHT = None
code = qrcode.QRCode()
def read_until(stream, find):
result = b""
while len(c := stream.read(1)) > 0:
if c == find:
return result
result += c
def discard_until(stream, find):
_ = read_until(stream, find)
def parse_xml_stream(s, accept_tags, group_by, max_items=3):
tag = []
text = b""
count = 0
current = {}
while True:
char = s.read(1)
if len(char) == 0:
break
if char == b"<":
next_char = s.read(1)
# Discard stuff like <?xml vers...
if next_char == b"?":
discard_until(s, b">")
continue
# Detect <![CDATA
elif next_char == b"!":
s.read(1) # Discard [
discard_until(s, b"[") # Discard CDATA[
text = read_until(s, b"]")
discard_until(s, b">") # Discard ]>
gc.collect()
elif next_char == b"/":
current_tag = read_until(s, b">")
top_tag = tag[-1]
# Populate our result dict
if top_tag in accept_tags:
current[top_tag.decode("utf-8")] = text.decode("utf-8")
# If we've found a group of items, yield the dict
elif top_tag == group_by:
yield current
current = {}
count += 1
if count == max_items:
return
tag.pop()
text = b""
gc.collect()
continue
else:
current_tag = read_until(s, b">")
if not current_tag.endswith(b"/"):
tag += [next_char + current_tag.split(b" ")[0]]
text = b""
else:
text += char
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)
graphics.set_pen(1)
graphics.rectangle(ox, oy, size, size)
graphics.set_pen(0)
for x in range(size):
for y in range(size):
if code.get_module(x, y):
graphics.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size)
# A function to get the data from an RSS Feed, this in case BBC News.
def get_rss():
try:
stream = urequest.urlopen(URL)
output = list(parse_xml_stream(stream, [b"title", b"description", b"guid", b"pubDate"], b"item"))
return output
except OSError as e:
print(e)
return []
feed = None
def update():
global feed
# Gets Feed Data
feed = get_rss()
def draw():
global feed
graphics.set_font("bitmap8")
# Clear the screen
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(0)
# Draws 2 articles from the feed if they're available.
if len(feed) > 0:
# Title
graphics.set_pen(graphics.create_pen(200, 0, 0))
graphics.rectangle(0, 0, WIDTH, 40)
graphics.set_pen(1)
graphics.text("Headlines from BBC News:", 10, 10, 320, 3)
graphics.set_pen(4)
graphics.text(feed[0]["title"], 10, 70, WIDTH - 150, 3 if graphics.measure_text(feed[0]["title"]) < WIDTH else 2)
graphics.text(feed[1]["title"], 130, 260, WIDTH - 140, 3 if graphics.measure_text(feed[1]["title"]) < WIDTH else 2)
graphics.set_pen(3)
graphics.text(feed[0]["description"], 10, 135 if graphics.measure_text(feed[0]["title"]) < 650 else 90, WIDTH - 150, 2)
graphics.text(feed[1]["description"], 130, 320 if graphics.measure_text(feed[1]["title"]) < 650 else 230, WIDTH - 145, 2)
graphics.line(10, 215, WIDTH - 10, 215)
code.set_text(feed[0]["guid"])
draw_qr_code(WIDTH - 110, 65, 100, code)
code.set_text(feed[1]["guid"])
draw_qr_code(10, 265, 100, code)
graphics.set_pen(graphics.create_pen(200, 0, 0))
graphics.rectangle(0, HEIGHT - 20, WIDTH, 20)
else:
graphics.set_pen(4)
graphics.rectangle(0, (HEIGHT // 2) - 20, WIDTH, 40)
graphics.set_pen(1)
graphics.text("Unable to display news feed!", 5, (HEIGHT // 2) - 15, WIDTH, 2)
graphics.text("Check your network settings in secrets.py", 5, (HEIGHT // 2) + 2, WIDTH, 2)
graphics.update()

Wyświetl plik

@ -1,121 +0,0 @@
import gc
import random
from urllib import urequest
from ujson import load
import qrcode
gc.collect() # We're really gonna need that RAM!
graphics = None
WIDTH = 0
HEIGHT = 0
FILENAME = "random-joke.jpg"
JOKE_IDS = "https://pimoroni.github.io/feed2image/jokeapi-ids.txt"
JOKE_IMG = "https://pimoroni.github.io/feed2image/jokeapi-{}.json"
UPDATE_INTERVAL = 60
gc.collect() # Claw back some RAM!
# We don't have the RAM to store the list of Joke IDs in memory.
# the first line of `jokeapi-ids.txt` is a COUNT of IDs.
# Grab it, then pick a random line between 0 and COUNT.
# Seek to that line and ...y'know... there's our totally random joke ID
joke = []
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)
graphics.set_pen(1)
graphics.rectangle(ox, oy, size, size)
graphics.set_pen(0)
for x in range(size):
for y in range(size):
if code.get_module(x, y):
graphics.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size)
def update():
global joke
try:
socket = urequest.urlopen(JOKE_IDS)
except OSError:
return
# Get the first line, which is a count of the joke IDs
number_of_lines = int(socket.readline().decode("ascii"))
print("Total jokes {}".format(number_of_lines))
# Pick a random joke (by its line number)
line = random.randint(0, number_of_lines)
print("Getting ID from line {}".format(line))
for x in range(line): # Throw away lines to get where we need
socket.readline()
# Read our chosen joke ID!
random_joke_id = int(socket.readline().decode("ascii"))
socket.close()
print("Random joke ID: {}".format(random_joke_id))
url = JOKE_IMG.format(random_joke_id)
gc.collect()
# Grab the data
try:
socket = urequest.urlopen(url)
gc.collect()
j = load(socket)
socket.close()
joke = j
del j
gc.collect()
except OSError as e:
print(e)
def draw():
code = qrcode.QRCode()
graphics.set_pen(1)
graphics.clear()
if joke:
if joke['type'] == "single":
graphics.set_pen(4)
graphics.text(joke['joke'], 10, 10, WIDTH - 75, 5)
if joke['type'] == "twopart":
graphics.set_pen(4)
graphics.text(joke['setup'], 10, 10, WIDTH - 75, 5)
graphics.set_pen(3)
graphics.text(joke['delivery'], 10, 250, WIDTH - 75, 5)
graphics.set_pen(0)
# Donate link QR
code.set_text("https://github.com/sponsors/Sv443")
draw_qr_code(WIDTH - 75, HEIGHT - 75, 75, code)
graphics.text("curated by jokeapi.dev", 10, HEIGHT - 15, WIDTH, 1)
graphics.text("donate <3", WIDTH - 65, HEIGHT - 12, WIDTH, 1)
else:
graphics.set_pen(4)
graphics.rectangle(0, (HEIGHT // 2) - 20, WIDTH, 40)
graphics.set_pen(1)
graphics.text("Unable to display random joke!", 5, (HEIGHT // 2) - 15, WIDTH, 2)
graphics.text("Check your network settings in secrets.py", 5, (HEIGHT // 2) + 2, WIDTH, 2)
graphics.update()

Wyświetl plik

@ -1,3 +0,0 @@
# secrets.py should contain:
WIFI_SSID = ""
WIFI_PASSWORD = ""

Wyświetl plik

@ -1,101 +0,0 @@
import machine
import ntptime
# Length of time between updates in minutes.
UPDATE_INTERVAL = 15
graphics = None
rtc = machine.RTC()
time_string = None
words = ["it", "d", "is", "m", "about", "l", "half", "c", "quarter", "b", "to", "u", "past", "n", "one",
"two", "three", "four", "five", "six", "eleven", "ten", "nine", "eight", "seven", "rm", "twelve", "rt", "O'Clock", "q"]
def approx_time(hours, minutes):
nums = {0: "twelve", 1: "one", 2: "two",
3: "three", 4: "four", 5: "five", 6: "six",
7: "seven", 8: "eight", 9: "nine", 10: "ten",
11: "eleven", 12: "twelve"}
if hours == 12:
hours = 0
if minutes > 0 and minutes < 8:
return "it is about " + nums[hours] + " O'Clock"
elif minutes >= 8 and minutes < 23:
return "it is about quarter past " + nums[hours]
elif minutes >= 23 and minutes < 38:
return "it is about half past " + nums[hours]
elif minutes >= 38 and minutes < 53:
return "it is about quarter to " + nums[hours + 1]
else:
return "it is about " + nums[hours + 1] + " O'Clock"
def update():
global time_string
# grab the current time from the ntp server and update the Pico RTC
try:
ntptime.settime()
except OSError:
print("Unable to contact NTP server")
current_t = rtc.datetime()
time_string = approx_time(current_t[4] - 12 if current_t[4] > 12 else current_t[4], current_t[5])
# Splits the string into an array of words for displaying later
time_string = time_string.split()
print(time_string)
def draw():
global time_string
graphics.set_font("bitmap8")
WIDTH, HEIGHT = graphics.get_bounds()
# Clear the screen
graphics.set_pen(1)
graphics.clear()
graphics.set_pen(6)
# Values for the layout and spacing
if WIDTH == 640: # Inky Frame 4.0"
default_x = 5
x = default_x
y = 10
line_space = 70
letter_space = 40
elif WIDTH == 800:
default_x = 5
x = default_x
y = 70
line_space = 60
letter_space = 50
else: # Inky Frame 5.7"
default_x = 20
x = default_x
y = 40
line_space = 65
letter_space = 35
scale = 5
spacing = 2
for word in words:
if word in time_string:
graphics.set_pen(0)
else:
graphics.set_pen(graphics.create_pen(220, 220, 220))
for letter in word:
text_length = graphics.measure_text(letter, scale, spacing)
if not x + text_length <= WIDTH:
y += line_space
x = default_x
graphics.text(letter.upper(), x, y, 640, scale=scale, spacing=spacing)
x += letter_space
graphics.update()

Wyświetl plik

@ -1,33 +0,0 @@
# Control the brightness of Inky Frame's LEDs using PWM
# More about PWM / frequency / duty cycle here:
# https://projects.raspberrypi.org/en/projects/getting-started-with-the-pico/7
from machine import Pin, PWM
from time import sleep
led_activity = PWM(Pin(6))
# we're just PWMing the activity LED in this demo
# but here are the pins for the other LEDs for reference
led_connect = PWM(Pin(7))
led_a = PWM(Pin(11))
led_b = PWM(Pin(12))
led_c = PWM(Pin(13))
led_d = PWM(Pin(14))
led_e = PWM(Pin(15))
led_activity.freq(1000)
leds = [led_activity, led_connect, led_a, led_b, led_c, led_d, led_e]
n = 0
while True:
for _ in range(2):
for duty in range(65025, 2):
leds[n].duty_u16(duty)
sleep(0.0001)
for duty in range(65025, 0, -2):
leds[n].duty_u16(duty)
sleep(0.0001)
n += 1
n %= len(leds)

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 5.4 KiB

Wyświetl plik

@ -1,26 +0,0 @@
# This simple example shows how to read and write from the SD card on Inky Frame.
from machine import Pin, SPI
import sdcard
import os
# set up the SD card
sd_spi = SPI(0, sck=Pin(18, Pin.OUT), mosi=Pin(19, Pin.OUT), miso=Pin(16, Pin.OUT))
sd = sdcard.SDCard(sd_spi, Pin(22))
os.mount(sd, "/sd")
# create a file and add some text
# if this file already exists it will be overwritten
with open("/sd/inkytest.txt", "w") as f:
f.write("Hello Inky!\r\n")
f.close()
# open the file and append some text (useful for logging!)
with open("/sd/inkytest.txt", "a") as f:
f.write("We're appending some text\r\n")
f.close()
# read the file and print the contents to the console
with open("/sd/inkytest.txt", "r") as f:
data = f.read()
print(data)
f.close()

Wyświetl plik

@ -1,58 +0,0 @@
include_directories(${CMAKE_CURRENT_LIST_DIR}/../../)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Essential
include(pimoroni_i2c/micropython)
include(pimoroni_bus/micropython)
# Pico Graphics Essential
include(hershey_fonts/micropython)
include(bitmap_fonts/micropython)
include(picographics/micropython)
# Pico Graphics Extra
include(pngdec/micropython)
include(jpegdec/micropython)
include(qrcode/micropython/micropython)
# Sensors & Breakouts
include(micropython-common-breakouts)
include(pcf85063a/micropython)
# Utility
include(adcfft/micropython)
include(wakeup/micropython)
# Configure wakeup for Inky Frame
target_compile_definitions(usermod_wakeup INTERFACE
-DWAKEUP_HAS_RTC=1
-DWAKEUP_HAS_SHIFT_REGISTER=1
-DWAKEUP_PIN_MASK=0b01000100
-DWAKEUP_PIN_DIR=0b01000100
-DWAKEUP_PIN_VALUE=0b01000100
)
# LEDs & Matrices
include(plasma/micropython)
# Servos & Motors
include(pwm/micropython)
include(servo/micropython)
include(encoder/micropython)
include(motor/micropython)
include(modules_py/modules_py)
copy_module(inky_frame.py)
# C++ Magic Memory
include(cppmem/micropython)
# Disable build-busting C++ exceptions
include(micropython-disable-exceptions)

Wyświetl plik

@ -14,6 +14,7 @@ target_include_directories(usermod_${MOD_NAME} INTERFACE
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
-DMODULE_${MOD_NAME_UPPER}_ENABLED=1
-DPICO_INCLUDE_RTC_DATETIME=1
)
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME})

Wyświetl plik

@ -40,21 +40,4 @@ const mp_obj_module_t wakeup_user_cmodule = {
.globals = (mp_obj_dict_t*)&mp_module_wakeup_globals,
};
#if MICROPY_VERSION <= 70144
MP_REGISTER_MODULE(MP_QSTR_wakeup, wakeup_user_cmodule, MODULE_WAKEUP_ENABLED);
#else
MP_REGISTER_MODULE(MP_QSTR_wakeup, wakeup_user_cmodule);
#endif
void runtime_init_latch(void) {
runtime_wakeup_gpio_state = gpio_get_all();
gpio_init_mask(PICO_WAKEUP_PIN_MASK);
gpio_set_dir_masked(PICO_WAKEUP_PIN_MASK, PICO_WAKEUP_PIN_DIR);
gpio_put_masked(PICO_WAKEUP_PIN_MASK, PICO_WAKEUP_PIN_VALUE);
};
// After runtime_init_early_resets, PICO_RUNTIME_INIT_EARLY_RESETS ?
PICO_RUNTIME_INIT_FUNC_HW(runtime_init_latch, "00101");
// Too early?
// PICO_RUNTIME_INIT_FUNC_HW(runtime_init_latch, PICO_RUNTIME_INIT_EARLIEST);

Wyświetl plik

@ -1,164 +1,3 @@
# Inky Frame (MicroPython) <!-- omit in toc -->
Most of your interaction with Inky Frame will be via the PicoGraphics library, but we have created an `inky_frame` module to help you read the onboard A, B, C, D and E buttons and control the LEDs.
- [Pico Graphics](#pico-graphics)
- [Colour \& Dithering](#colour--dithering)
- [Images \& JPEGs](#images--jpegs)
- [Buttons \& LEDs](#buttons--leds)
- [Status LEDs](#status-leds)
- [Battery Power \& RTC](#battery-power--rtc)
- [Function Reference](#function-reference)
- [Wakeup States](#wakeup-states)
- [RTC](#rtc)
- [Other](#other)
- [RAM Usage](#ram-usage)
- [Accessing The Framebuffer](#accessing-the-framebuffer)
## Pico Graphics
You can draw on Inky Frame using our PicoGraphics display library.
- [PicoGraphics MicroPython function reference](../modules/picographics)
### Colour & Dithering
Inky Frame supports eight native colours, those that it's physically capable of displaying. These are:
* `BLACK` = 0
* `WHITE` = 1
* `GREEN` = 2
* `BLUE` = 3
* `RED` = 4
* `YELLOW` = 5
* `ORANGE` = 6
* `TAUPE` = 7 (only used for dither on 4.0" and 5.7" displays †)
:info: † - the "taupe" colour (also known as clear) is the colour used to reset the display to a default, clear state. We misuse this as an extra colour on 4.0" and 5.7", but the 7.3" display clear colour is a sort of muddy, greenish gradient that's not consistent enough. You can stil use it, by all means, but it wont be considered for dithering.
These colours are available as constants in the `inky_frame` module so you don't have to remember which number corresponds to which, eg:
```python
display.set_pen(inky_frame.YELLOW)
```
You can use colours outside of these eight by using `create_pen(r, g, b)` or `create_pen_hsv(h, s, v)`. eg:
```python
my_colour = display.create_pen(255, 128, 0)
display.set_pen(my_colour)
```
PicoGraphics will do its best to match your colour choice by mixing the available colours with a dither pattern. This works best on large areas of colour such as backgrounds, big UI elements or chunky text. See the `inky_frame_dithering.py` example for a demonstration.
:info: Due to the lack of light blue or pink native colours, Inky Frame isn't very good at reproducing pink, purple, violet or light blue, cyan and teal.
### Images & JPEGs
You can use the `jpegdec` (JPEG decoding) module to display JPEG files on Inky, but you should be aware of some caveats:
1. JPEGs are compressed and lossy, a small JPEG displayed on Inky might show random specks of colour where you don't expect them as PicoGraphics tries its best to dither noise to the nearest available colours.
2. JPEGs are, by default, dithered to the 7 (or 8 on 4.0 and 5.7) available colours. This uses "ordered dither," which is fast, but not very pretty.
3. You can turn off dithering with `jpeg.decode(dither=False)` for a posterised effect.
## Buttons & LEDs
Inky Frame has five user buttons labelled A to E, with LEDs. The buttons are connected via a shift-register and available in the `inky_frame` module as `button_a`, `button_b`, `button_c`, `button_d` and `button_e`.
Each button has some convenient methods for checking if it's pressed:
* `raw()` - get the current state of the button with no debounce (returns `True` the first time it's called if the corresponding button)
* `read()` - read the current button state, observing debounce (50ms)
Additionally each button has some methods for controlling its associated LED:
* `led_on()` - turn the LED on
* `led_off()` - turn the LED off
* `led_toggle()` - toggle the LED
* `led_brightness(0.5)` - set the LED brightness (from 0.0 to 1.0)
### Status LEDs
In addition to the button LEDs there are two status LEDs, `busy` and `wifi`, which are available as:
* `led_busy` - connected to pin `LED_BUSY`
* `led_wifi` - connected to pin `LED_WIFI`
These are instances of the `pimoroni.PWMLED` class, and have the following methods:
* `on()` - turn the LED on
* `off()` - turn the LED off
* `toggle()` - toggle the LED
* `brightness(0.5)` - set the LED brightness (from 0.0 to 1.0)
:info: `toggle()` is provided for compatibility with the `Pin()` class. It's a little odd, since it changes the LED brightness to `1.0 - current_brightness`.
## Battery Power & RTC
When running on battery power, Inky Frame's buttons cause it to wake from a power-off state and start running your code. Additionally an onboard PCF85063A real-time clock runs continuously from battery and can wake up your Frame automatically.
The Inky Frame library includes a number of convenience functions to set the clock and sleep your device. If you want accurate time you must check if the clock is set - usually a simple check against the year works - connect to a network, and set via NTP (Network Time Protocol).
Inky Frame technically has *two* real-time clocks, the external RTC we've added - which remains continuously powered by battery - and the internal RTC of the Pico W. For convenience and compatibility with other code examples we recommend using the external RTC only to set the time on the internal one which makes functions like `time.localtime()` work as you'd expect.
```python
import time
import machine
import inky_frame
inky_frame.pcf_to_pico_rtc() # Sync Inky RTC time to Pico's RTC
year, month, day, dow, hour, minute, second, _ = machine.RTC().datetime()
if year < 2023:
# Connect to network
inky_frame.set_time() # Sets both the Inky and Pico RTC
print(time.localtime())
```
### Function Reference
#### Wakeup States
For your convenience these wakeup state functions also check the *current* state of their associated event, this allows you to run code from Thonny with a button held down - for example - to test how your deployed code will behave on battery-
* `inky_frame.woken_by_rtc()` - Returns `True` if the RTC caused a wakeup, or if the RTC ALARM is currently raised.
* `inky_frame.woken_by_button()` - Returns `True` if a button caused a wakeup, or if a button is currently pressed.
* `inky_frame.woken_by_ext_trigger()` Returns `True` if the external trigger caused a wakeup, or if the trigger is currently asserted.
#### RTC
* `inky_frame.set_time()` - Attempt to run `ntptime.settime()` and set the time on both RTCs
* `inky_frame.pcf_to_pico_rtc()` - Sync from Inky's RTC to the Pico W's RTC
* `inky_frame.pico_rtc_to_pcf()` - Sync from Pico W's RTC to Inky's RTC (sometime useful since Thonny sets the Pico W RTC automatically)
* `inky_frame.sleep_for(minutes)` - Set the RTC alarm for a number of minutes and cut the power. This will completely power off the Pico W, but leave the Inky RTC running to wake it back up.
:info: You can access all Inky RTC (PCF85063A) functions via `inky_frame.rtc`
#### Other
* `inky_frame.turn_off()` - Cut the power to the Pico W (on battery only), only an alarm event or a button press can wake it back up.
## RAM Usage
Both Inky 4.0" and 5.7" use only the Pico's onboard RAM. It's quite cozy. The frame buffers are 3-bits-per-pixel, making Inky 4.0"
On 7.3" we had to add a PSRAM chip to act as the display's framebuffer. Right now it acts exclusively as a framebuffer, but that frees up some Pico RAM to work with so you can do more with that 7.3" panel.
The Inky frame buffer sizes are as follows:
* 4.0" - 640x400 - 96,000 bytes.
* 5.7" - 600x448 - 100,800 bytes.
* 7.3" - 800x480 - 144,000 bytes.
Since MicroPython on a Pico W has only 166k that would have left *just* 22k on 7.3", instead you get (almost) all 166k to play with since the PicoGraphics instance itself uses only 8544 bytes!
### Accessing The Framebuffer
PicoGraphics has undocumented support for accessing its raw framebuffer using `memoryview(graphics)`.
This is useful for copying raw binary images (effectively valid Inky frame buffers saved to a file) avoiding JPEG compression and so forth. For some dicussion about why and how you might do this, see: https://github.com/pimoroni/pimoroni-pico/issues/681
:warning: This *does not work* for Inky 7.3, since there is no framebuffer in memory. PicoGraphics will raise a `ValueError: No local framebuffer.` if you try. We aim to fix this with some hardfault handling sorcery.
Moved to: [https://github.com/pimoroni/inky-frame/blob/main/docs/inky_frame.md](https://github.com/pimoroni/inky-frame/blob/main/docs/inky_frame.md)

Wyświetl plik

@ -1,181 +0,0 @@
from pimoroni import ShiftRegister, PWMLED
from machine import Pin, I2C, RTC
from wakeup import get_shift_state, reset_shift_state
from micropython import const
import pcf85063a
import ntptime
import time
BLACK = const(0)
WHITE = const(1)
GREEN = const(2)
BLUE = const(3)
RED = const(4)
YELLOW = const(5)
ORANGE = const(6)
TAUPE = const(7)
SR_CLOCK = const(8)
SR_LATCH = const(9)
SR_OUT = const(10)
LED_A = const(11)
LED_B = const(12)
LED_C = const(13)
LED_D = const(14)
LED_E = const(15)
LED_BUSY = const(6)
LED_WIFI = const(7)
HOLD_VSYS_EN = const(2)
RTC_ALARM = const(2)
EXTERNAL_TRIGGER = const(1)
EINK_BUSY = const(0)
SHIFT_STATE = get_shift_state()
reset_shift_state()
i2c = I2C(0)
rtc = pcf85063a.PCF85063A(i2c)
i2c.writeto_mem(0x51, 0x00, b'\x00') # ensure rtc is running (this should be default?)
rtc.enable_timer_interrupt(False)
vsys = Pin(HOLD_VSYS_EN)
vsys.on()
def woken_by_rtc():
mask = (1 << RTC_ALARM)
return bool(sr.read() & mask) or bool(SHIFT_STATE & mask)
def woken_by_ext_trigger():
mask = (1 << EXTERNAL_TRIGGER)
return bool(sr.read() & mask) or bool(SHIFT_STATE & mask)
def woken_by_button():
return bool(sr.read() & 0b11111000) or bool(SHIFT_STATE & 0b11111000)
def pico_rtc_to_pcf():
# Set the PCF85063A to the time stored by Pico W's RTC
year, month, day, dow, hour, minute, second, _ = RTC().datetime()
rtc.datetime((year, month, day, hour, minute, second, dow))
def pcf_to_pico_rtc():
# Set Pico W's RTC to the time stored by the PCF85063A
t = rtc.datetime()
# BUG ERRNO 22, EINVAL, when date read from RTC is invalid for the Pico's RTC.
try:
RTC().datetime((t[0], t[1], t[2], t[6], t[3], t[4], t[5], 0))
return True
except OSError:
return False
def sleep_for(minutes):
year, month, day, hour, minute, second, dow = rtc.datetime()
# if the time is very close to the end of the minute, advance to the next minute
# this aims to fix the edge case where the board goes to sleep right as the RTC triggers, thus never waking up
if second >= 55:
minute += 1
# Can't sleep beyond a month, so clamp the sleep to a 28 day maximum
minutes = min(minutes, 40320)
# Calculate the future alarm date; first, turn the current time into seconds since epoch
sec_since_epoch = time.mktime((year, month, day, hour, minute, second, dow, 0))
# Add the required minutes to this
sec_since_epoch += minutes * 60
# And convert it back into a more useful tuple
(ayear, amonth, aday, ahour, aminute, asecond, adow, adoy) = time.localtime(sec_since_epoch)
# And now set the alarm as before, now including the day
rtc.clear_alarm_flag()
rtc.set_alarm(0, aminute, ahour, aday)
rtc.enable_alarm_interrupt(True)
turn_off()
# Simulate sleep while on USB power
while minutes > 0:
time.sleep(60)
minutes -= 1
def turn_off():
time.sleep(0.1)
vsys.off()
def set_time():
# Set both Pico W's RTC and PCF85063A to NTP time
ntptime.settime()
pico_rtc_to_pcf()
class Button:
def __init__(self, sr, idx, led, debounce=50):
self.sr = sr
self.startup_state = bool(SHIFT_STATE & (1 << idx))
self.led = PWMLED(led)
self.led.off()
self._idx = idx
self._debounce_time = debounce
self._changed = time.ticks_ms()
self._last_value = None
def led_on(self):
self.led.on()
def led_off(self):
self.led.off()
def led_brightness(self, brightness):
self.led.brightness(brightness)
def led_toggle(self):
self.led.toggle()
def read(self):
if self.startup_state:
self.startup_state = False
return True
value = self.raw()
if value != self._last_value and time.ticks_ms() - self._changed > self._debounce_time:
self._last_value = value
self._changed = time.ticks_ms()
return value
return False
def raw(self):
if self.startup_state:
self.startup_state = False
return True
return self.sr[self._idx] == 1
@property
def is_pressed(self):
return self.raw()
sr = ShiftRegister(SR_CLOCK, SR_LATCH, SR_OUT)
button_a = Button(sr, 7, LED_A)
button_b = Button(sr, 6, LED_B)
button_c = Button(sr, 5, LED_C)
button_d = Button(sr, 4, LED_D)
button_e = Button(sr, 3, LED_E)
led_busy = PWMLED(LED_BUSY)
led_wifi = PWMLED(LED_WIFI)

Wyświetl plik

@ -26,7 +26,7 @@ On the releases page you'll find a bunch of different .uf2 files for use on diff
| Tiny 2040 8MB | **tiny2040_8mb-vx.x.x-pimoroni-micropython.uf2** | Includes support for the increased flash memory on this board |
| Enviro Urban, Indoor, Weather & Grow | **enviro-vx.x.x-pimoroni-micropython.uf2** | For a .uf2 with examples built in, go to the [Enviro releases page](https://github.com/pimoroni/enviro/releases)! |
| Galactic Unicorn | **galactic_unicorn-vx.x.x-pimoroni-micropython.uf2** | |
| Inky Frame | **inky_frame-vx.x.x-pimoroni-micropython.uf2** | |
| Inky Frame | | See: [https://github.com/pimoroni/inky-frame/releases/latest](https://github.com/pimoroni/inky-frame/releases/latest) |
| Badger 2040 W | **badger2040w-vx.x.x-pimoroni-micropython.uf2** or **pimoroni-badger2040w-v0.0.1-micropython-with-badger-os.uf2** | :warning: Badger OS will overwrite your files!
| Badger 2040 | **badger2040-vx.x.x-pimoroni-micropython.uf2** or **pimoroni-badger2040-v0.0.1-micropython-with-badger-os.uf2** | :warning: Badger OS will overwrite your files!
| Cosmic Unicorn | **cosmic_unicorn-vx.x.x-pimoroni-micropython.uf2** | |