Pimoroni offers a range of its own products based around the Raspberry Pi Pico microcontroller, usually with example software that will at least allow you to use them without much work, although the software is often out-of-date and the documentation inadequate to show clearly how it could be modified to do anything other than the default actions.
Some of the products provide a menu system to allow different functions to be selected with buttons on the device, but they use different ways of programming this.
This article looks at the menu system on the Inky Frame series of e-ink displays in an attempt to see how it can be modified to include a new custom item.
Breaking down the code
main.py
firstly imports the appropriate class for screen as DISPLAY
(select the appropriate line for the screen you are using) and initiates it into the graphics
variable, obtaining the WIDTH
and HEIGHT
of the display from it.
# 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()
After the launcher
function is defined, all LEDs are turned off—button and warning LEDs—in case any are still on. ih
is imported from inky-helper
.
ih.clear_button_leds()
ih.led_warn.off()
If A and E are being pressed together or there is no state.json
file, launcher()
is called to display a menu then loop until a button is pressed.
if ih.inky_frame.button_a.read() and ih.inky_frame.button_e.read():
launcher()
If a button is pressed in that loop, the update_state
method in inky_helper
is called to save the name of the selected option into the state.json
file, then machine.reset()
is called to do a hard reset, e.g.
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 the state.json
file does exist, the load_state
method in inky_helper
reads the contents of the file, converts it to JSON then into a dictionary and saves it as the global state
variable. It only has one entry with the key "run" holding the name of the file (without the .py
extension)
The launch_app
method in inky_helper
is then called from main
, which imports the file for the selected menu item, under the "run" key in the state
dictionary, into the global app
variable and calls update state
to save this back into state.json
.
if ih.file_exists("state.json"):
ih.load_state()
ih.launch_app(ih.state['run'])
Three values are then injected into the imported app
file: graphics
, WIDTH
and HEIGHT
.
ih.app.graphics = graphics
ih.app.WIDTH = WIDTH
ih.app.HEIGHT = HEIGHT
For this to work, each app file must have somewhere in the variable initiation section:
graphics = None
WIDTH = None
HEIGHT = None
An attempt is then made to create a WiFi network connection using the credentials in the secrets.py
file, which should define just two variables: WIFI_SSID
and WIFI_PASSWORD
, which are self-explanatory.
There is a forced garbage collection with gc.collect()
to clear some memory, then an infinite while
loop begins that goes through the sequence:
- call an
update()
method in the imported file - turn the warn LED on
- call a
draw()
method in the imported file - turn the warn LED off
- get the
UPDATE_INTERVAL
constant from the imported file then call thesleep
method ininky_helper
to set the RTC wake-up timer go into a sleep mode if on battery or do a regularsleep
if on USB power. ## Creating a new menu item
To try this out, this section will create a new file called test.py
that will just put up a test message on the screen with the current date and time.
First, import just the parts needed to save memory:
from ntptime import settime
from machine import RTC
In order to create a new item to replace one in the default menu, the file to be imported will need to contain, apart from the new application code:
-
variables
graphics
,WIDTH
andHEIGHT
initialised toNone
.
graphics = None WIDTH = None HEIGHT = None
-
a constant
UPDATE_INTERVAL
set to the time in minutes between updates of the screen.
UPDATE_INTERVAL = 5 # set to 5 minutes so don't have to wait too long to see whether it's working
-
instantiate any other variables required for this file.
time_string = None rtc = RTC()
-
a method
update()
that gets the new data (a network/Internet connection should be available).
def update(): global time_string try: settime() except OSError: print("Unable to contact NTP server") rtc_time = rtc.datetime() year, month, mday, weekday, hour, minute, second, subsecond = rtc_time if year < 2023: time_string = "Unable to update time" else: time_string = f"{mday:0=2}/{month:0=2}/{year:0=2} at {hour:0=2}:{minute:0=2}"
-
a method
draw()
that refreshes the screen with the new data.
def draw(): global time_string graphics.set_font("sans") graphics.set_pen(0) graphics.clear() graphics.set_pen(1) graphics.set_thickness(5) graphics.text("Test", int(WIDTH / 2) - 120, int(HEIGHT / 2) - 20, 600, 4) graphics.set_thickness(3) graphics.text(time_string, 0, HEIGHT - 20, 600, 1) graphics.update()
Some extra code can be added to the end to mimic the menu's actions so that this can be run as a standalone application, which can be useful for debugging without having to trigger it from the menu each time, which would require two lengthy screen refreshes:
if __name__ == '__main__':
import inky_helper as ih
import gc
from utime import sleep
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
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")
gc.collect()
update()
draw()
ih.led_warn.off()
To make it work from the menu, in main.py
, one of the default menu items needs to be replaced by the new code. In launcher()
:
- replace the description text for one of the menu items with a description of the new widget, but keep the capital letter at the start, which indicates to the user which button to press to select it, e.g.
graphics.text("A. Test", 35, HEIGHT - 325, 600, 3)
- in the
while True
loop, replace the string in the item equivalent to the one in which the description text has been replaced with the filename (without the.py
extension) of the new widget, e.g.
ih.update_state("test")
Here is the full listing of the test.py
file with some helpful comments added.
# test.py
from ntptime import settime
from machine import RTC
# Set variables required by menu
graphics = None
WIDTH = None
HEIGHT = None
UPDATE_INTERVAL = 5
# Instantiate time_string and real time clock
time_string = None
rtc = RTC()
# Update data
def update():
global time_string
print("Update data...")
# Set RTC time from network
try:
settime()
except OSError:
print("Unable to contact NTP server")
# Get time tuple and unpack it
rtc_time = rtc.datetime()
year, month, mday, weekday, hour, minute, second, subsecond = rtc_time
# If year is before 2023 assume time could not be updated
if year < 2023:
time_string = "Unable to update time"
else:
# Construct time string, padding each value with leading zeros to 2 digits
time_string = f"{mday:0=2}/{month:0=2}/{year:0=2} at {hour:0=2}:{minute:0=2}"
# Draw screen
def draw():
global time_string
graphics.set_font("sans")
# Make screen black
graphics.set_pen(0)
graphics.clear()
# Make writing white
graphics.set_pen(1)
# Write test text
graphics.set_thickness(5)
graphics.text("Test", int(WIDTH / 2) - 120, int(HEIGHT / 2) - 20, 600, 4)
# Write date and time
graphics.set_thickness(3)
graphics.text(time_string, 0, HEIGHT - 20, 600, 1)
graphics.update()
print("Draw screen")
# Mimic call from menu if running this file as standalone
if __name__ == '__main__':
import inky_helper as ih
import gc
from utime import sleep
# Change this next line if you are not running the 4" Inky Frame
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
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")
gc.collect()
print("Updating...")
update()
print("Drawing...")
draw()
print("Clearing LEDs...")
ih.led_warn.off()
Top comments (0)