Inky Frame: MicroPython Examples.

Co-authored-by: thirdr <ryan@pimoroni.com>
pull/445/head
Phil Howard 2022-07-14 14:43:51 +01:00
rodzic e519396876
commit afea765b71
8 zmienionych plików z 791 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,63 @@
# Pico Enviro+ MicroPython Examples <!-- omit in toc -->
- [PicoGraphics](#picographics)
- [Examples](#examples)
- [Daily Activity](#daily-activity)
- [News](#news)
- [PlaceKitten](#placekitten)
- [Quote of the Day](#quote-of-the-day)
- [Random Joke](#random-joke)
- [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 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.
Finally for examples loading images, you'll need `sdcard.mpy` from `common/lib`. You should place this file in `lib` on your Pico W.
### Daily Activity
[inky_frame_daily_activity.py](inky_frame_daily_activity.py)
### 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
### 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

Wyświetl plik

@ -0,0 +1,3 @@
SSID = "YOUR_WIFI_SSID"
PSK = "YOUR_WIFI_PASSWORD"
COUNTRY = "YOUR_COUNTRY_CODE"

Wyświetl plik

@ -0,0 +1,138 @@
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
from machine import Pin
from pimoroni_i2c import PimoroniI2C
from pcf85063a import PCF85063A
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_INKY_FRAME)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_font("cursive")
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_pen(4)
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)

Wyświetl plik

@ -0,0 +1,188 @@
from picographics import PicoGraphics, DISPLAY_INKY_FRAME
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
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_INKY_FRAME)
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(490, 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(490, 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)

Wyświetl plik

@ -0,0 +1,68 @@
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
"""
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://placekitten.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

@ -0,0 +1,169 @@
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
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_INKY_FRAME)
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

@ -0,0 +1,88 @@
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
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 = "random-joke.jpg"
JOKE_IDS = "https://pimoroni.github.io/feed2image/jokeapi-ids.txt"
JOKE_IMG = "https://pimoroni.github.io/feed2image/jokeapi-{}-600x448.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)
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

@ -0,0 +1,74 @@
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
"""
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.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()