A dedicated weather display running on a Raspberry Pi Zero 2W with a HyperPixel 4 Square (720×720 px) touchscreen.
A single lean Rust binary fetches live 10-day forecasts from Open-Meteo and renders directly to the Linux framebuffer — no GUI stack, no browser, no window manager.
- Two-view interface — swipe left/right to switch between Overview and Detail
- Overview — 10-day hi/lo temperature chart with area fill, precipitation bar chart, and a 3-day summary strip
- Detail — scrollable day cards (swipe up/down), each with hourly temperature + precipitation chart, condition, wind, UV, humidity, pressure, and sunrise/sunset
- Live clock with blinking colon, updated every 500 ms
- Zero runtime dependencies — one statically-linked binary, writes directly to
/dev/fb0 - Honest error states — shows a loading screen on first boot; if the network is unavailable it retries every 5 min and switches to 30 min once data is fetched
| Component | Model |
|---|---|
| SBC | Raspberry Pi Zero 2W |
| Display | Pimoroni HyperPixel 4 Square (pim470) — touch model |
| Resolution | 720 × 720 px, RGB565 framebuffer |
| Power | 5V 2.5A USB-C |
| Gesture | Action |
|---|---|
| Swipe left | Overview → Detail |
| Swipe right | Detail → Overview |
| Swipe up (in Detail) | Show later days |
| Swipe down (in Detail) | Show earlier days |
Open-Meteo API (free, no key required)
│
│ HTTPS/JSON (fetched on startup, then every 30 min)
▼
weather-square (Rust binary)
│
├── Render thread — wakes every 500 ms, draws frame → /dev/fb0
├── Fetch thread — fetches Open-Meteo on startup, retries every 5 min until first success, then every 30 min
└── Touch thread — reads /dev/input/event*, detects swipes
Shared state is an Arc<Mutex<WeatherData>> for weather data and Arc<AtomicU8> for UI state (active tab, scroll offset).
- Set up your Pi — see Pi Setup below.
- Build — cross-compile on your dev machine (see Building below).
- Deploy — copy the binary to the Pi and install the systemd service (see Deployment below).
All configuration is via environment variables. Defaults point at Mullsjö, Sweden — change them to your location.
| Variable | Default | Description |
|---|---|---|
OPEN_METEO_LAT |
57.9171 |
Latitude |
OPEN_METEO_LON |
13.8783 |
Longitude |
OPEN_METEO_TZ |
Europe/Stockholm |
Timezone (Open-Meteo format) |
LOCATION_NAME |
MULLSJÖ, SWEDEN |
Display name shown in header |
FB_PATH |
/dev/fb0 |
Framebuffer device path |
TOUCH_PATH |
(auto) | Touch input event device — auto-detected if not set |
Set them in the systemd unit (see Deployment) or export before running manually.
Touch device auto-detection: at startup the binary scans
/proc/bus/input/devicesfor the first device that reportsBTN_TOUCHcapability and opens it automatically. You only need to setTOUCH_PATHif auto-detection picks the wrong device.
Finding your timezone string: run
timedatectl list-timezoneson any Linux machine, or browse the tz database list.
You'll need Rust installed on your dev machine. If you don't have it:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shCross-compilation uses cargo-zigbuild, which only requires Zig — no Docker needed.
# Install zig and cargo-zigbuild (once)
brew install zig # macOS — on Linux: see ziglang.org/download
cargo install cargo-zigbuild
# Add the ARM64 Linux target (once)
rustup target add aarch64-unknown-linux-gnu
# Build
cargo zigbuild --release --target aarch64-unknown-linux-gnuBinary output: target/aarch64-unknown-linux-gnu/release/weather-square
Replace <user> and <pi-hostname> with your Pi's username and hostname (or IP).
# Stop the service first (binary is locked while running)
ssh <user>@<pi-hostname> 'sudo systemctl stop weather-square'
scp target/aarch64-unknown-linux-gnu/release/weather-square <user>@<pi-hostname>:/home/<user>/weather-square
ssh <user>@<pi-hostname> 'sudo systemctl start weather-square'Create /etc/systemd/system/weather-square.service on the Pi:
[Unit]
Description=Weather Square
After=network-online.target
Wants=network-online.target
[Service]
ExecStartPre=-/bin/sh -c 'printf "\033[?25l" > /dev/tty1'
ExecStart=/home/<user>/weather-square
Restart=always
RestartSec=5
User=<user>
Environment=OPEN_METEO_LAT=57.9171
Environment=OPEN_METEO_LON=13.8783
Environment=OPEN_METEO_TZ=Europe/Stockholm
Environment=LOCATION_NAME=MULLSJÖ, SWEDEN
[Install]
WantedBy=multi-user.targetNote:
TOUCH_PATHis not needed — the binary auto-detects the touchscreen at startup. Only set it if you need to override (e.g. multiple input devices). Checkjournalctl -u weather-squareto see which device was detected.
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now weather-squareCheck it's running:
sudo systemctl status weather-square
sudo journalctl -u weather-square -fFlash Raspberry Pi OS Lite (64-bit) using Raspberry Pi Imager. In the imager's advanced options, set your hostname, username, password, and WiFi credentials before writing — the Pi will boot headless and SSH-ready.
git clone https://github.com/pimoroni/hyperpixel4 --depth=1
cd hyperpixel4
sudo ./install.shSelect Square and Touch when prompted, then reboot. Verify with:
ls /dev/fb0 # should exist
cat /dev/urandom > /dev/fb0 # should show noise on screen (Ctrl+C to stop)On Raspberry Pi OS Bookworm, edit /boot/firmware/cmdline.txt (older releases: /boot/cmdline.txt) and append to the existing single line:
consoleblank=0 logo.nologo=1
The binary needs access to the framebuffer and the touchscreen:
sudo usermod -aG video,input <user>Log out and back in for the change to take effect.
The binary auto-detects the touchscreen by scanning /proc/bus/input/devices for the first device with BTN_TOUCH capability — no manual configuration needed. The selected device is logged at startup:
journalctl -u weather-square | grep '\[touch\]'
# [touch] auto-discovered device: /dev/input/event2
# [touch] opened /dev/input/event2If you ever need to override it, set TOUCH_PATH=/dev/input/eventN in the systemd service.
| Crate | Purpose |
|---|---|
fontdue |
Software font rasterization |
chrono |
Local time and date parsing |
minreq |
Minimal HTTPS client (rustls) |
serde + serde_json |
JSON deserialization |
No GPU, no windowing system, no async runtime.
Bundled in assets/fonts/:
- Unbounded — headers and large temperatures
- Space Mono — all body text and labels
This project was implemented exclusively using Claude Code as an agentic coding assistant — from the initial Rust code through hardware debugging, cross-compilation, deployment, and open-sourcing. It was a deliberate experiment to see how far agentic AI can take an embedded systems project end-to-end.
MIT
