We've updated our popular PiOLED script for use with the Mini PiTFT, a 135x240 Color TFT add-on for your Raspberry Pi. This cute little display has two tactile buttons on GPIO pins that we'll use to make a simple user-interface display for your Pi-Hole.

Install 2x20 Header
If you are using a Pi Zero, you'll need to solder on or somehow attach a 2x20 header so you can plug in the Pi OLED. Use either a plain 2x20 header and solder it in using an iron + some solder...

Or you can use Hammer headers which do not need soldering.

Either way, you'll want to end up with something like this:
Setup Virtual Environment with Blinka
Starting with the Bookworm version of Raspberry Pi OS, you will need to install your python modules in a virtual environment. Even on earlier versions, it's still recommended to install a virtual environment. You can find more information in the Python Virtual Environment Usage on Raspberry Pi guide. To Install and activate the virtual environment, use the following commands:
sudo apt install python3-venv sudo apt-get install -y python3-pip python3 -m venv pihole --system-site-packages source pihole/bin/activate pip3 install --upgrade adafruit-python-shell wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py sudo -E env PATH=$PATH python3 raspi-blinka.py
You will need to activate the virtual environment every time the Pi is rebooted. To activate it:
source pihole/bin/activate
To deactivate, you can use deactivate
, but leave it active for now.
Python Setup
This guide assumes that you've gotten your Raspberry Pi up and running, have CircuitPython installed, and have installed CircuitPython libraries for the Mini PiTFT. If not, follow the steps in the guide below and come back to this page when you've completed them.
source pihole/bin/activate pip3 install adafruit-circuitpython-rgb-display sudo apt-get install fonts-dejavu python3-pil python3-numpy
Update the stats.py program
Here's the new stats.py code which uses the Mini PiTFT.
Create a new file using nano ~pi/stats.py
and paste the code below in. Then, save the code.
# SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries # SPDX-FileCopyrightText: 2025 Mikey Sklar for Adafruit Industries # # SPDX-License-Identifier: MIT # Copyright (c) 2017 Adafruit Industries # Author: Brent Rubell, Mikey Sklar # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # This example is for use on (Linux) computers that are using CPython with # Adafruit Blinka to support CircuitPython libraries. CircuitPython does # not support PIL/pillow (python imaging library)! # -*- coding: utf-8 -*- # Import Python System Libraries import time import json import subprocess # Import Requests Library import requests #Import Blinka import digitalio import board # Import Python Imaging Library from PIL import Image, ImageDraw, ImageFont import adafruit_rgb_display.st7789 as st7789 API_URL = "http://localhost/api/stats/summary" # Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4): cs_pin = digitalio.DigitalInOut(board.D17) dc_pin = digitalio.DigitalInOut(board.D25) reset_pin = None # Config for display baudrate (default max is 24mhz): BAUDRATE = 64000000 # Setup SPI bus using hardware SPI: spi = board.SPI() # Create the ST7789 display: disp = st7789.ST7789( spi, dc_pin, cs_pin, reset_pin, 135, 240, baudrate=BAUDRATE, x_offset=53, y_offset=40, rotation=90 ) # Create blank image for drawing. # Make sure to create image with mode 'RGB' for full color. CANVAS_WIDTH = disp.height CANVAS_HEIGHT = disp.width image = Image.new('RGB', (CANVAS_WIDTH, CANVAS_HEIGHT)) draw = ImageDraw.Draw(image) # Load default font (or replace with a TTF if desired) FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" font = ImageFont.truetype(FONT_PATH, 24) buttonA = digitalio.DigitalInOut(board.D23) buttonA.switch_to_input() while True: # Draw a black filled box to clear the image. draw.rectangle((0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), outline=0, fill=(0, 0, 0)) # Shell scripts for system monitoring from here: # https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load cmd = "hostname -I | cut -d' ' -f1" IP = "IP: " + subprocess.check_output(cmd, shell=True).decode("utf-8").strip() cmd = "hostname | tr -d '\\n'" HOST = subprocess.check_output(cmd, shell=True).decode("utf-8") cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'" CPU = subprocess.check_output(cmd, shell=True).decode("utf-8") cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB %.2f%%\", $3,$2,$3*100/$2 }'" MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8") cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%d GB %s\", $3,$2,$5}'" Disk = subprocess.check_output(cmd, shell=True).decode("utf-8") cmd = ( "cat /sys/class/thermal/thermal_zone0/temp | " "awk '{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}'" ) Temp = subprocess.check_output(cmd, shell=True).decode("utf-8") # Pi Hole data! try: r = requests.get(API_URL, timeout=5) r.raise_for_status() data = r.json() DNSQUERIES = data["queries"]["total"] ADSBLOCKED = data["queries"]["blocked"] CLIENTS = data["clients"]["total"] except (KeyError, requests.RequestException, json.JSONDecodeError): DNSQUERIES = None ADSBLOCKED = None CLIENTS = None y = top = 5 x = 5 if not buttonA.value: # just button A pressed draw.text((x, y), IP, font=font, fill="#FFFF00") y += font.getbbox(IP)[3] draw.text((x, y), CPU, font=font, fill="#FFFF00") y += font.getbbox(CPU)[3] draw.text((x, y), MemUsage, font=font, fill="#00FF00") y += font.getbbox(MemUsage)[3] draw.text((x, y), Disk, font=font, fill="#0000FF") y += font.getbbox(Disk)[3] draw.text((x, y), Temp, font=font, fill="#FF00FF") y += font.getbbox(Temp)[3] else: draw.text((x, y), IP, font=font, fill="#FFFF00") y += font.getbbox(IP)[3] draw.text((x, y), HOST, font=font, fill="#FFFF00") y += font.getbbox(HOST)[3] if ADSBLOCKED is not None: txt = f"Ads Blocked: {ADSBLOCKED}" draw.text((x, y), txt, font=font, fill="#00FF00") y += font.getbbox(txt)[3] if CLIENTS is not None: txt = f"Clients: {CLIENTS}" draw.text((x, y), txt, font=font, fill="#0000FF") y += font.getbbox(txt)[3] if DNSQUERIES is not None: txt = f"DNS Queries: {DNSQUERIES}" draw.text((x, y), txt, font=font, fill="#FF00FF") y += font.getbbox(txt)[3] # Display image. disp.image(image) time.sleep(.1)
You'll notice it's very similar to the original stats.py, but we've added PiHole API support. Here's how we did that:
First up, Pi Hole stats are available through the web server, in JSON format, so we need to add web requests and JSON parsing to Python. Then set the URL for the API access, which is localhost (the same computer) and through the admin page:
# Import Python System Libraries import time import json import subprocess # Import Requests Library import requests api_url = 'http://localhost/api/stats/summary'
We load up the nice DejaVuSans font here. Note that we have to have the full path of the file.
# Alternatively load a TTF font. Make sure the .ttf font file is in the # same directory as the python script! # Some other nice fonts to try: http://www.dafont.com/bitmap.php FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" font = ImageFont.truetype(FONT_PATH, 20)
This is where we grab the API data. I put it in a try block, so it would retry in case the API access failed for some reason
# Pi Hole data! try: r = requests.get(api_url) data = json.loads(r.text) DNSQUERIES = data['dns_queries_today'] ADSBLOCKED = data['ads_blocked_today'] CLIENTS = data['unique_clients'] except KeyError: time.sleep(1) continue
If you want to print out different info, run this small script in python to see what is available:
import json import requests api_url = 'http://localhost/api/stats/summary' r = requests.get(api_url) data = json.loads(r.text) print(data)
Since the MiniTFT has two tactile push-buttons, we modified the script to print out extra information from the Raspberry Pi when you press the top button.
if not buttonA.value: # just button A pressed draw.text((x, y), IP, font=font, fill="#FFFF00") y += font.getsize(IP)[1] draw.text((x, y), CPU, font=font, fill="#FFFF00") y += font.getsize(CPU)[1] draw.text((x, y), MemUsage, font=font, fill="#00FF00") y += font.getsize(MemUsage)[1] draw.text((x, y), Disk, font=font, fill="#0000FF") y += font.getsize(Disk)[1] draw.text((x, y), "DNS Queries: {}".format(DNSQUERIES), font=font, fill="#FF00FF") else: draw.text((x, y), IP, font=font, fill="#FFFF00") y += font.getsize(IP)[1] draw.text((x, y), HOST, font=font, fill="#FFFF00") y += font.getsize(HOST)[1] draw.text((x, y), "Ads Blocked: {}".format(str(ADSBLOCKED)), font=font, fill="#00FF00") y += font.getsize(str(ADSBLOCKED))[1] draw.text((x, y), "Clients: {}".format(str(CLIENTS)), font=font, fill="#0000FF") y += font.getsize(str(CLIENTS))[1] draw.text((x, y), "DNS Queries: {}".format(str(DNSQUERIES)), font=font, fill="#FF00FF") y += font.getsize(str(DNSQUERIES))[1]
This step is critical to being able to access the Pi Hole API. Press enter (leave blank) when prompted for API password.
sudo pihole setpassword
Once you have the script saved, you can run it with
python3 stats.py
Look on the Mini PiTFT to make sure you see your IP address along with some statistics from Pi-Hole.
Pushing the top button should display extra statistics about the Pi such as its hostname, CPU load, memory utilization, disk usage, and DNS queries.
Page last edited July 04, 2025
Text editor powered by tinymce.