|
1 | 1 | # SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries |
| 2 | +# SPDX-FileCopyrightText: 2025 Mikey Sklar for Adafruit Industries |
2 | 3 | # |
3 | 4 | # SPDX-License-Identifier: MIT |
4 | 5 |
|
| 6 | +# Copyright (c) 2017 Adafruit Industries |
| 7 | +# Author: Brent Rubell, Mikey Sklar |
| 8 | +# |
| 9 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 10 | +# of this software and associated documentation files (the "Software"), to deal |
| 11 | +# in the Software without restriction, including without limitation the rights |
| 12 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 13 | +# copies of the Software, and to permit persons to whom the Software is |
| 14 | +# furnished to do so, subject to the following conditions: |
| 15 | +# |
| 16 | +# The above copyright notice and this permission notice shall be included in |
| 17 | +# all copies or substantial portions of the Software. |
| 18 | +# |
| 19 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 20 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 21 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 22 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 23 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 24 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 25 | +# THE SOFTWARE. |
| 26 | + |
| 27 | +# This example is for use on (Linux) computers that are using CPython with |
| 28 | +# Adafruit Blinka to support CircuitPython libraries. CircuitPython does |
| 29 | +# not support PIL/pillow (python imaging library)! |
| 30 | + |
| 31 | + |
5 | 32 | # -*- coding: utf-8 -*- |
6 | 33 | # Import Python System Libraries |
7 | 34 | import time |
|
19 | 46 | from PIL import Image, ImageDraw, ImageFont |
20 | 47 | import adafruit_rgb_display.st7789 as st7789 |
21 | 48 |
|
22 | | -API_TOKEN = "YOUR_API_TOKEN_HERE" |
23 | | -api_url = "http://localhost/admin/api.php?summaryRaw&auth="+API_TOKEN |
| 49 | +API_URL = "http://localhost/api/stats/summary" |
24 | 50 |
|
25 | 51 | # Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4): |
26 | | -cs_pin = digitalio.DigitalInOut(board.CE0) |
| 52 | +cs_pin = digitalio.DigitalInOut(board.D17) |
27 | 53 | dc_pin = digitalio.DigitalInOut(board.D25) |
28 | 54 | reset_pin = None |
29 | 55 |
|
|
34 | 60 | spi = board.SPI() |
35 | 61 |
|
36 | 62 | # Create the ST7789 display: |
37 | | -disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE, |
38 | | - width=135, height=240, x_offset=53, y_offset=40) |
| 63 | +disp = st7789.ST7789( |
| 64 | + spi, |
| 65 | + dc_pin, |
| 66 | + cs_pin, |
| 67 | + reset_pin, |
| 68 | + 135, |
| 69 | + 240, |
| 70 | + baudrate=BAUDRATE, |
| 71 | + x_offset=53, |
| 72 | + y_offset=40, |
| 73 | + rotation=90 |
| 74 | +) |
39 | 75 |
|
40 | 76 | # Create blank image for drawing. |
41 | 77 | # Make sure to create image with mode 'RGB' for full color. |
42 | | -height = disp.width # we swap height/width to rotate it to landscape! |
43 | | -width = disp.height |
44 | | -image = Image.new('RGB', (width, height)) |
45 | | -rotation = 90 |
46 | | - |
47 | | -# Get drawing object to draw on image. |
| 78 | +CANVAS_WIDTH = disp.height |
| 79 | +CANVAS_HEIGHT = disp.width |
| 80 | +image = Image.new('RGB', (CANVAS_WIDTH, CANVAS_HEIGHT)) |
48 | 81 | draw = ImageDraw.Draw(image) |
49 | 82 |
|
50 | | -# Draw a black filled box to clear the image. |
51 | | -draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0)) |
52 | | -disp.image(image, rotation) |
53 | | -# Draw some shapes. |
54 | | -# First define some constants to allow easy resizing of shapes. |
55 | | -padding = -2 |
56 | | -top = padding |
57 | | -bottom = height-padding |
58 | | -# Move left to right keeping track of the current x position for drawing shapes. |
59 | | -x = 0 |
60 | | - |
61 | | - |
62 | | -# Alternatively load a TTF font. Make sure the .ttf font file is in the |
63 | | -# same directory as the python script! |
64 | | -# Some other nice fonts to try: http://www.dafont.com/bitmap.php |
65 | | -font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 24) |
66 | | - |
67 | | -# Turn on the backlight |
68 | | -backlight = digitalio.DigitalInOut(board.D22) |
69 | | -backlight.switch_to_output() |
70 | | -backlight.value = True |
71 | | - |
72 | | -# Add buttons as inputs |
| 83 | +# Load default font (or replace with a TTF if desired) |
| 84 | +FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" |
| 85 | +font = ImageFont.truetype(FONT_PATH, 24) |
| 86 | + |
73 | 87 | buttonA = digitalio.DigitalInOut(board.D23) |
74 | 88 | buttonA.switch_to_input() |
75 | 89 |
|
76 | 90 | while True: |
77 | 91 | # Draw a black filled box to clear the image. |
78 | | - draw.rectangle((0, 0, width, height), outline=0, fill=0) |
| 92 | + draw.rectangle((0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), outline=0, fill=(0, 0, 0)) |
79 | 93 |
|
80 | 94 | # Shell scripts for system monitoring from here: |
81 | 95 | # https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load |
82 | | - cmd = "hostname -I | cut -d\' \' -f1" |
83 | | - IP = "IP: "+subprocess.check_output(cmd, shell=True).decode("utf-8") |
84 | | - cmd = "hostname | tr -d \'\\n\'" |
| 96 | + cmd = "hostname -I | cut -d' ' -f1" |
| 97 | + IP = "IP: " + subprocess.check_output(cmd, shell=True).decode("utf-8").strip() |
| 98 | + cmd = "hostname | tr -d '\\n'" |
85 | 99 | HOST = subprocess.check_output(cmd, shell=True).decode("utf-8") |
86 | 100 | cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'" |
87 | 101 | CPU = subprocess.check_output(cmd, shell=True).decode("utf-8") |
88 | 102 | cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB %.2f%%\", $3,$2,$3*100/$2 }'" |
89 | 103 | MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8") |
90 | 104 | cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%d GB %s\", $3,$2,$5}'" |
91 | 105 | Disk = subprocess.check_output(cmd, shell=True).decode("utf-8") |
92 | | - cmd = "cat /sys/class/thermal/thermal_zone0/temp | awk \'{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}\'" # pylint: disable=line-too-long |
| 106 | + cmd = ( |
| 107 | + "cat /sys/class/thermal/thermal_zone0/temp | " |
| 108 | + "awk '{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}'" |
| 109 | + ) |
93 | 110 | Temp = subprocess.check_output(cmd, shell=True).decode("utf-8") |
94 | 111 |
|
95 | | - |
96 | 112 | # Pi Hole data! |
97 | 113 | try: |
98 | | - r = requests.get(api_url) |
99 | | - data = json.loads(r.text) |
100 | | - DNSQUERIES = data['dns_queries_today'] |
101 | | - ADSBLOCKED = data['ads_blocked_today'] |
102 | | - CLIENTS = data['unique_clients'] |
103 | | - except KeyError: |
104 | | - time.sleep(1) |
105 | | - continue |
106 | | - |
107 | | - y = top |
| 114 | + r = requests.get(API_URL, timeout=5) |
| 115 | + r.raise_for_status() |
| 116 | + data = r.json() |
| 117 | + DNSQUERIES = data["queries"]["total"] |
| 118 | + ADSBLOCKED = data["queries"]["blocked"] |
| 119 | + CLIENTS = data["clients"]["total"] |
| 120 | + except (KeyError, requests.RequestException, json.JSONDecodeError): |
| 121 | + DNSQUERIES = None |
| 122 | + ADSBLOCKED = None |
| 123 | + CLIENTS = None |
| 124 | + |
| 125 | + y = top = 5 |
| 126 | + x = 5 |
108 | 127 | if not buttonA.value: # just button A pressed |
109 | 128 | draw.text((x, y), IP, font=font, fill="#FFFF00") |
110 | 129 | y += font.getbbox(IP)[3] |
|
121 | 140 | y += font.getbbox(IP)[3] |
122 | 141 | draw.text((x, y), HOST, font=font, fill="#FFFF00") |
123 | 142 | y += font.getbbox(HOST)[3] |
124 | | - draw.text((x, y), "Ads Blocked: {}".format(str(ADSBLOCKED)), font=font, fill="#00FF00") |
125 | | - y += font.getbbox(str(ADSBLOCKED))[3] |
126 | | - draw.text((x, y), "Clients: {}".format(str(CLIENTS)), font=font, fill="#0000FF") |
127 | | - y += font.getbbox(str(CLIENTS))[3] |
128 | | - draw.text((x, y), "DNS Queries: {}".format(str(DNSQUERIES)), font=font, fill="#FF00FF") |
129 | | - y += font.getbbox(str(DNSQUERIES))[3] |
| 143 | + if ADSBLOCKED is not None: |
| 144 | + txt = f"Ads Blocked: {ADSBLOCKED}" |
| 145 | + draw.text((x, y), txt, font=font, fill="#00FF00") |
| 146 | + y += font.getbbox(txt)[3] |
| 147 | + if CLIENTS is not None: |
| 148 | + txt = f"Clients: {CLIENTS}" |
| 149 | + draw.text((x, y), txt, font=font, fill="#0000FF") |
| 150 | + y += font.getbbox(txt)[3] |
| 151 | + if DNSQUERIES is not None: |
| 152 | + txt = f"DNS Queries: {DNSQUERIES}" |
| 153 | + draw.text((x, y), txt, font=font, fill="#FF00FF") |
| 154 | + y += font.getbbox(txt)[3] |
130 | 155 |
|
131 | 156 | # Display image. |
132 | | - disp.image(image, rotation) |
| 157 | + disp.image(image) |
133 | 158 | time.sleep(.1) |
0 commit comments