Skip to content

Instantly share code, notes, and snippets.

@muzll0dr
Last active April 28, 2021 02:59
Show Gist options
  • Save muzll0dr/8145bec5d8af8a80faf4e613c89a44c6 to your computer and use it in GitHub Desktop.
Save muzll0dr/8145bec5d8af8a80faf4e613c89a44c6 to your computer and use it in GitHub Desktop.
A better stats script for the Adafruit PiHole tutorial using the Mini Color PiTFT display.
# -*- coding: utf-8 -*-
# Import Python System Libraries
import time
import json
import subprocess
import math
# 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/admin/api.php'
api_pass = '{apikey}'
api_disable = api_url + '?disable&auth=' + api_pass
api_enable = api_url + '?enable&auth=' + api_pass
# Used for a temp disable
disable_minutes = 5
disable_seconds = disable_minutes * 60
api_temp_disable = 'http://localhost/admin/api.php?disable=' + str(disable_seconds) + '&auth=' + api_pass
# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
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, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE,
width=135, height=240, x_offset=53, y_offset=40)
# Create blank image for drawing.
# Make sure to create image with mode 'RGB' for full color.
height = disp.width # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new('RGB', (width, height))
rotation = 90
# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)
# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = -2
top = padding
bottom = height-padding
# Move left to right keeping track of the current x position for drawing shapes.
x = 0
# 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 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 24)
# Turn on the backlight
backlight = digitalio.DigitalInOut(board.D22)
backlight.switch_to_output()
backlight.value = True
# Add buttons as inputs
buttonA = digitalio.DigitalInOut(board.D23)
buttonA.switch_to_input()
buttonB = digitalio.DigitalInOut(board.D24)
buttonB.switch_to_input()
# Get the IP and hostname one time.
cmd = "hostname -I | cut -d\' \' -f1"
IP = "IP: "+subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "hostname | tr -d \'\\n\'"
HOST = subprocess.check_output(cmd, shell=True).decode("utf-8")
# FUNCTIONS
def showStats():
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)
return
y = top
draw.rectangle((0, 0, width, height), outline=0, fill=0)
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), "Queries: {}".format(str(DNSQUERIES)), font=font, fill="#00FF00")
y += font.getsize(str(DNSQUERIES))[1]
draw.text((x, y), "Blocked: {}".format(str(ADSBLOCKED)), font=font, fill="#FF0000")
y += font.getsize(str(ADSBLOCKED))[1]
draw.text((x, y), "Clients: {}".format(str(CLIENTS)), font=font, fill="#FF00FF")
y += font.getsize(str(CLIENTS))[1]
disp.image(image, rotation)
def showPaused(seconds_left):
y = top
y += font.getsize(IP)[1]
draw.rectangle((0, 0, width, height), outline=0, fill=0)
msg = "DISABLED FOR" #define the first line
mywidth, hei = draw.textsize(msg, font=font) # determine how wide the first line is
draw.text(((width-mywidth)/2 ,y), msg, font=font, fill="#FF0000") # center and draw the first line
y += font.getsize(IP)[1] * 2 # create a line-height space (using IP because why not)
if (seconds_left < 61):
msg = str(seconds_left) + " SECONDS"
else:
minutes_left = math.ceil(seconds_left / 60)
msg = str(minutes_left) + " MINUTE"
if (minutes_left > 1):
msg += "S"
mywidth, hei = draw.textsize(msg, font=font)
draw.text(((width-mywidth)/2 ,y), msg, font=font, fill="#FF0000")
disp.image(image, rotation)
def showDisabled():
y = top
# Move it down a tidge. "I" here is just a random full-height character.
y += font.getsize('I')[1]
# Draw black display.
draw.rectangle((0, 0, width, height), outline=0, fill=0)
msg = "PIHOLE IS" #define the first line
mywidth, hei = draw.textsize(msg, font=font) # determine how wide the first line is
draw.text(((width-mywidth)/2 ,y), msg, font=font, fill="#FF0000") # center and draw the first line
y += font.getsize('I')[1] * 2 # create a line-height space (using IP because why not)
msg = "DISABLED"
mywidth, hei = draw.textsize(msg, font=font)
draw.text(((width-mywidth)/2 ,y), msg, font=font, fill="#FF0000")
disp.image(image, rotation)
def showCPUStats():
draw.rectangle((0, 0, width, height), outline=0, fill=0)
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\", $3, $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}\'" # pylint: disable=line-too-long
Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")
y = top
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), Temp, font=font, fill="#FF00FF")
disp.image(image, rotation)
buttonTimer = math.floor(time.time()) # used to ensure we don't get repeat button presses
def resetButtonTimer():
buttonTimer = math.floor(time.time())
## Main Loop
cycleSeconds = 3
cycleTime = math.ceil(time.time()) + cycleSeconds
shouldCycle = True
screen = 'stats'
status = 'enabled'
showStats()
resumeTime = False
while True:
### Button Presses
if not buttonA.value and not buttonB.value:
# use this to toggle the flipflop or just show stats
# this will ensure a delay so it doesn't cycle between true/false rapidly
if (buttonTimer < math.floor(time.time())):
shouldCycle = not shouldCycle
resetButtonTimer()
elif not buttonA.value:
# if ((math.floor(time.time()) - buttonTimer) <= 1):
# continue;
if (status == 'paused' or status == 'disabled'):
requests.get(api_enable)
status = 'enabled'
elif (status == 'enabled'):
requests.get(api_disable)
resumeTime = math.ceil(time.time()) + disable_seconds
status = 'paused'
resetButtonTimer()
elif not buttonB.value:
if (status == 'disabled'):
requests.get(api_enable)
status = 'enabled'
else:
requests.get(api_disable)
status = 'disabled'
showDisabled() # putting this here since this is a static screen
resetButtonTimer()
### Logic
# this is probably all goign to have to move below the buttons, yeah??
if (status == 'enabled' and shouldCycle):
if (cycleTime < math.ceil(time.time())):
if (screen != 'cpu'):
screen = 'cpu'
else:
screen = 'stats'
cycleTime = math.ceil(time.time()) + cycleSeconds
if (screen == 'cpu'):
showCPUStats()
elif (screen == 'stats'):
showStats()
elif (status == 'enabled'):
showStats()
elif (status == 'paused'):
# first check to see if we need to unpause, otherwise show the pause screen
seconds_left = resumeTime - (math.ceil(time.time()))
if (seconds_left <= 0):
requests.get(api_enable)
status = 'enabled'
resumeTime = False
else:
showPaused(seconds_left)
else:
# we show the diabled screen when we hit the disable
# button, and since it's a static screen, we're not
# going to do anything unless the status changes.
doNothing = True
time.sleep(.2)
@muzll0dr
Copy link
Author

TODO:

  • make the pressed button timer work better

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment