A weather dashboard for the Pimoroni Inky Frame 7.3" E Ink display that fetches live weather data from the Yr.no API.
This is similar to the "card" view that YR provides, eg. https://www.yr.no/en/content/2-2654991/card.html
(the values shown by the card can be used to verify the data shown on the Inky Frame)
See the YR docs on how to generate a card view URL for your location: https://developer.yr.no/doc/guides/available-widgets/
- Pimoroni Inky Frame 7.3" (Spectra 6 display)
- MicroSD card
- WiFi connection
- USB power (this version keeps the CPU awake to serve the status webserver; battery operation is not supported)
The snap-on case was sourced from MakerWorld and printed using PLA.
See Pimoroni's installation guide to get the firmware installed: https://github.com/pimoroni/inky-frame
In order to run the code locally ie. to test the display output using ascii.py (assumes asdf):
asdf install
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
- Format the SD card as FAT32
- Copy the
./weather-iconsdirectory onto the SD card - Insert the SD card into the Inky Frame
Configuration is split into two files:
secrets.py- WiFi credentials only. Never pushed over the network.config.py- per-device settings (location, refresh interval). Round-tripped viapush.py.
Create secrets.py on the Inky Frame's Pico:
# secrets.py - WiFi credentials only
WIFI_SSID = "your-wifi-ssid"
WIFI_PASSWORD = "your-wifi-password"And config.py:
# config.py - Per-device runtime configuration
LOCATION_NAME = "Location name"
LATITUDE = 00.0000 # max 4 decimal places
LONGITUDE = 00.0000 # max 4 decimal places
UTC_OFFSET_HOURS = 0 # eg. for BST use 1, for CEST use 2
SLEEP_INTERVAL_MINUTES = 60 # minutes between fetch + render cycles
MAX_STALE_MINUTES = 360 # keep last good reading on screen this long (6h) before showing an errorOn a failed fetch the device keeps the last good reading on screen and retries
sooner, backing off (5 → 10 → 20 → 40 min, capped at SLEEP_INTERVAL_MINUTES)
rather than blanking the panel. A transient outage therefore never replaces the
weather with an error. Only once the displayed data is older than
MAX_STALE_MINUTES does it fall back to the error screen - and that ceiling
holds even while WiFi is down, so a sustained outage can never strand day-old
weather on the panel.
Bootstrap once over USB (with Thonny or mpremote): copy main.py, webserver.py, dashboard.py, screen.py, logger.py, ascii.py, weather_utils.py, config.py, and secrets.py to the device.
main.py is the single entry point - it installs the log capture, mounts the SD card, connects to WiFi, starts the status webserver, then enters the fetch/render loop.
After the first boot, all subsequent code/config changes can be pushed over WiFi using push.py (see below) - no need to re-plug USB.
push.py is a small dev-side script that POSTs files to the device's webserver and triggers a reboot. The device's IP is printed on boot - write it to .push_host (one line) so push.py finds it automatically. (Or pass --host <ip>, or set the INKY_HOST env var.)
echo "192.168.1.42" > .push_host
./push.py code # push main.py and reboot
./push.py file dashboard.py # push any .py file and reboot
./push.py file webserver.py dashboard.py # push multiple files, reboot once at the end
./push.py config fetch # download device config.py into _device/config.py
./push.py config push # upload _device/config.py and reboot
./push.py reboot # just rebootsecrets.py is intentionally not pushable - credentials are bootstrapped once over USB and never traverse the LAN.
While the device is running, point a browser at http://<device-ip>/ for a live status page (auto-refreshes every 5s):
- Device: IP, local time, uptime, WiFi RSSI, free heap.
- Weather fetch: location, last fetch age + status (with failure count), age of the data currently shown (flagged
stale, retryingduring an outage), next refresh ETA. - Footer links:
/status(JSON snapshot),/logs(rotating log on SD, 4 KB RAM fallback),/logs?download=1(save asdevice.log),/ascii(ASCII render of the latest weather),/config(currentconfig.pysource). - Reboot button (POSTs to
/reboot).
Useful for curl:
curl http://<device-ip>/status | jq
curl http://<device-ip>/logsThe webserver pauses briefly (~30 s) during each e-ink redraw - refreshes will hang until the render completes, then resume.
Every print() is mirrored to /sd/logs/current.log (rotating to previous.log at 64 KB, so SD usage stays bounded at ~128 KB). Logs survive reboots, so if the device fails mid-boot you can either pull the card or hit /logs?download=1 after recovery to see what happened. Lines before the NTP sync are timestamped with seconds since boot ([+12.345s]); wall-clock timestamps thereafter.
The code can also be run in a standard Python environment (ie. not on the Inky Frame) to see the weather data printed in ASCII format in the console, this is pretty handy for testing without having to plug in the device / wait for the display to refresh for each change.
ascii.py reads LOCATION_NAME, LATITUDE, LONGITUDE, and UTC_OFFSET_HOURS from config.py (the same file pushed to the device). No secrets.py is needed for the ASCII harness.
Then run:
python ascii.py================================================================================
Weather for Location-Name
Thu 4th Dec
--------------------------------------------------------------------------------
Temperature: 7°C
Icon: wi-cloudy.jpg
Cloud: 100%
Pressure: 996 hPa
Humidity: 84%
Precipitation: 0.0 mm
Wind: 6 m/s
Direction: WSW
--------------------------------------------------------------------------------
Time Temp Icon Precip Wind
--------------------------------------------------------------------------------
13-18 8°C wi-cloudy.jpg 0.0 mm 6 m/s
18-00 4°C wi-night-clear.jpg 0.0 mm 4 m/s
00-06 2°C wi-night-alt-cloudy.jpg 0.0 mm 3 m/s
06-12 2°C wi-cloudy.jpg 0.1 mm 3 m/s
================================================================================
This project uses data from the Yr.no API. Make sure to comply with their Terms of Service.
Weather icons are from Erik Flowers' Weather Icons collection.