diff --git a/Pi_Hole_Ad_Blocker/mini_pitft_stats.py b/Pi_Hole_Ad_Blocker/mini_pitft_stats.py index 3d1349458..68e4576a9 100755 --- a/Pi_Hole_Ad_Blocker/mini_pitft_stats.py +++ b/Pi_Hole_Ad_Blocker/mini_pitft_stats.py @@ -1,7 +1,34 @@ # 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 @@ -19,11 +46,10 @@ from PIL import Image, ImageDraw, ImageFont import adafruit_rgb_display.st7789 as st7789 -API_TOKEN = "YOUR_API_TOKEN_HERE" -api_url = "http://localhost/admin/api.php?summaryRaw&auth="+API_TOKEN +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.CE0) +cs_pin = digitalio.DigitalInOut(board.D17) dc_pin = digitalio.DigitalInOut(board.D25) reset_pin = None @@ -34,54 +60,42 @@ 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) +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. -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. +CANVAS_WIDTH = disp.height +CANVAS_HEIGHT = disp.width +image = Image.new('RGB', (CANVAS_WIDTH, CANVAS_HEIGHT)) 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 +# 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, width, height), outline=0, fill=0) + 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") - cmd = "hostname | tr -d \'\\n\'" + 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") @@ -89,22 +103,27 @@ 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 + 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) - 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 - - y = top + 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] @@ -121,13 +140,19 @@ y += font.getbbox(IP)[3] draw.text((x, y), HOST, font=font, fill="#FFFF00") y += font.getbbox(HOST)[3] - draw.text((x, y), "Ads Blocked: {}".format(str(ADSBLOCKED)), font=font, fill="#00FF00") - y += font.getbbox(str(ADSBLOCKED))[3] - draw.text((x, y), "Clients: {}".format(str(CLIENTS)), font=font, fill="#0000FF") - y += font.getbbox(str(CLIENTS))[3] - draw.text((x, y), "DNS Queries: {}".format(str(DNSQUERIES)), font=font, fill="#FF00FF") - y += font.getbbox(str(DNSQUERIES))[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, rotation) + disp.image(image) time.sleep(.1) diff --git a/Pi_Hole_Ad_Blocker/stats.py b/Pi_Hole_Ad_Blocker/stats.py index d1021ba68..25e093404 100644 --- a/Pi_Hole_Ad_Blocker/stats.py +++ b/Pi_Hole_Ad_Blocker/stats.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries # SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries # SPDX-FileCopyrightText: 2017 James DeVito for Adafruit Industries +# SPDX-FileCopyrightText: 2025 Mikey Sklar for Adafruit Industries # # SPDX-License-Identifier: MIT @@ -29,44 +30,33 @@ # Adafruit Blinka to support CircuitPython libraries. CircuitPython does # not support PIL/pillow (python imaging library)! -# Import Python System Libraries -import json + import subprocess import time -# Import Requests Library import requests - -# Import Blinka from board import SCL, SDA import busio import adafruit_ssd1306 - -# Import Python Imaging Library from PIL import Image, ImageDraw, ImageFont -API_TOKEN = "YOUR_API_TOKEN_HERE" -api_url = "http://localhost/admin/api.php?summaryRaw&auth="+API_TOKEN +api_url = "http://localhost/api/stats/summary" # Create the I2C interface. i2c = busio.I2C(SCL, SDA) # Create the SSD1306 OLED class. -# The first two parameters are the pixel width and pixel height. Change these -# to the right size for your display! disp = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c) # Leaving the OLED on for a long period of time can damage it -# Set these to prevent OLED burn in -DISPLAY_ON = 10 # on time in seconds -DISPLAY_OFF = 50 # off time in seconds +DISPLAY_ON = 10 # on time in seconds +DISPLAY_OFF = 50 # off time in seconds # Clear display. disp.fill(0) disp.show() # Create blank image for drawing. -# Make sure to create image with mode '1' for 1-bit color. width = disp.width height = disp.height image = Image.new('1', (width, height)) @@ -77,27 +67,21 @@ # Draw a black filled box to clear the image. draw.rectangle((0, 0, width, height), outline=0, fill=0) -# 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 # Load nice silkscreen font font = ImageFont.truetype('/home/pi/slkscr.ttf', 8) while True: - # Draw a black filled box to clear the image. + # Clear the image buffer draw.rectangle((0, 0, width, height), outline=0, fill=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 | tr -d \'\\n\'" + # Shell scripts for system monitoring + cmd = "hostname -I | cut -d' ' -f1 | tr -d '\\n'" IP = subprocess.check_output(cmd, shell=True).decode("utf-8") - cmd = "hostname | tr -d \'\\n\'" + 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)}'" @@ -109,35 +93,30 @@ "\"Disk: %d/%dGB %s\", $3,$2,$5}'" Disk = subprocess.check_output(cmd, shell=True).decode("utf-8") - # Pi Hole data! + # 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 - - draw.text((x, top), "IP: " + str(IP) + - " (" + HOST + ")", font=font, fill=255) - draw.text((x, top + 8), "Ads Blocked: " + - str(ADSBLOCKED), font=font, fill=255) - draw.text((x, top + 16), "Clients: " + - str(CLIENTS), font=font, fill=255) - draw.text((x, top + 24), "DNS Queries: " + - str(DNSQUERIES), font=font, fill=255) - - # skip over original stats - # draw.text((x, top+8), str(CPU), font=font, fill=255) - # draw.text((x, top+16), str(MemUsage), font=font, fill=255) - # draw.text((x, top+25), str(Disk), font=font, fill=255) + r = requests.get(api_url, timeout=2) + r.raise_for_status() + data = r.json() + DNSQUERIES = data["queries"]["total"] + ADSBLOCKED = data["queries"]["blocked"] + CLIENTS = data["clients"]["total"] + except (KeyError, requests.RequestException): + DNSQUERIES = 0 + ADSBLOCKED = 0 + CLIENTS = 0 + + draw.text((x, top), "IP: " + IP + " (" + HOST + ")", font=font, fill=255) + draw.text((x, top + 8), "Ads Blocked: " + str(ADSBLOCKED), font=font, fill=255) + draw.text((x, top + 16), "Clients: " + str(CLIENTS), font=font, fill=255) + draw.text((x, top + 24), "DNS Queries: " + str(DNSQUERIES), font=font, fill=255) # Display image. disp.image(image) disp.show() time.sleep(DISPLAY_ON) + + # Blank screen to prevent burn-in disp.fill(0) disp.show() time.sleep(DISPLAY_OFF)