Skip to content

Commit e31accd

Browse files
authored
add sys_info_extended.py (rm-hull#163)
* feat: add enhanced sysinfo.py * feat: upgrade * feat: add font file, refer relative path * feat: change filename (sys_info2.py -> sys_info_extended.py) * lint * feat: README, CONTRIBUTING, docstring
1 parent 464812e commit e31accd

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

CONTRIBUTING.rst

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ Contributors
1919
* Michael Svanström (@squeezeday)
2020
* Tomislav Kopić (@Tkopic001)
2121
* Maciej Sokolowski (@matemaciek)
22+
* Sangho Kim (@rlaace423)

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ sprite_animation.py Using sprite maps for animation effects
7878
starfield.py 3D starfield simulation
7979
sys_histogram.py Display system information including a rolling histogram
8080
sys_info.py Display basic system information
81+
sys_info_extended.py Display detailed system information in graph format
8182
terminal.py Simple println capabilities
8283
tv_snow.py Example image-blitting
8384
tweet_scroll.py Using Twitter's Streaming API to display scrolling notifications

examples/fonts/DejaVuSansMono.ttf

333 KB
Binary file not shown.

examples/sys_info_extended.py

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# Copyright (c) 2023 Richard Hull and contributors
4+
# See LICENSE.rst for details.
5+
6+
"""
7+
Display detailed system information in graph format.
8+
It provides a display of CPU, memory, disk utilization, temperature, IP address and system Uptime.
9+
10+
Needs psutil (+ dependencies) installed::
11+
12+
$ sudo apt-get install python-dev
13+
$ sudo -H pip install psutil
14+
"""
15+
16+
import time
17+
from pathlib import Path
18+
from datetime import datetime
19+
from demo_opts import get_device
20+
from luma.core.render import canvas
21+
from PIL import ImageFont
22+
import psutil
23+
import subprocess as sp
24+
import socket
25+
from collections import OrderedDict
26+
27+
28+
def get_temp():
29+
temp = float(sp.getoutput("vcgencmd measure_temp").split("=")[1].split("'")[0])
30+
return temp
31+
32+
33+
def get_cpu():
34+
return psutil.cpu_percent()
35+
36+
37+
def get_mem():
38+
return psutil.virtual_memory().percent
39+
40+
41+
def get_disk_usage():
42+
usage = psutil.disk_usage("/")
43+
return usage.used / usage.total * 100
44+
45+
46+
def get_uptime():
47+
uptime = ("%s" % (datetime.now() - datetime.fromtimestamp(psutil.boot_time()))).split(".")[0]
48+
return "UpTime: %s" % (uptime)
49+
50+
51+
def find_single_ipv4_address(addrs):
52+
for addr in addrs:
53+
if addr.family == socket.AddressFamily.AF_INET: # IPv4
54+
return addr.address
55+
56+
57+
def get_ipv4_address(interface_name=None):
58+
if_addrs = psutil.net_if_addrs()
59+
60+
if isinstance(interface_name, str) and interface_name in if_addrs:
61+
addrs = if_addrs.get(interface_name)
62+
address = find_single_ipv4_address(addrs)
63+
return address if isinstance(address, str) else ""
64+
else:
65+
if_stats = psutil.net_if_stats()
66+
# remove loopback
67+
if_stats_filtered = {key: if_stats[key] for key, stat in if_stats.items() if "loopback" not in stat.flags}
68+
# sort interfaces by
69+
# 1. Up/Down
70+
# 2. Duplex mode (full: 2, half: 1, unknown: 0)
71+
if_names_sorted = [stat[0] for stat in sorted(if_stats_filtered.items(), key=lambda x: (x[1].isup, x[1].duplex), reverse=True)]
72+
if_addrs_sorted = OrderedDict((key, if_addrs[key]) for key in if_names_sorted if key in if_addrs)
73+
74+
for _, addrs in if_addrs_sorted.items():
75+
address = find_single_ipv4_address(addrs)
76+
if isinstance(address, str):
77+
return address
78+
79+
return ""
80+
81+
82+
def get_ip(network_interface_name):
83+
return "IP: %s" % (get_ipv4_address(network_interface_name))
84+
85+
86+
def format_percent(percent):
87+
return "%5.1f" % (percent)
88+
89+
90+
def draw_text(draw, margin_x, line_num, text):
91+
draw.text((margin_x, margin_y_line[line_num]), text, font=font_default, fill="white")
92+
93+
94+
def draw_bar(draw, line_num, percent):
95+
top_left_y = margin_y_line[line_num] + bar_margin_top
96+
draw.rectangle((margin_x_bar, top_left_y, margin_x_bar + bar_width, top_left_y + bar_height), outline="white")
97+
draw.rectangle((margin_x_bar, top_left_y, margin_x_bar + bar_width * percent / 100, top_left_y + bar_height), fill="white")
98+
99+
100+
def draw_bar_full(draw, line_num):
101+
top_left_y = margin_y_line[line_num] + bar_margin_top
102+
draw.rectangle((margin_x_bar, top_left_y, margin_x_bar + bar_width_full, top_left_y + bar_height), fill="white")
103+
draw.text((65, top_left_y - 2), "100 %", font=font_full, fill="black")
104+
105+
106+
def stats(device):
107+
with canvas(device) as draw:
108+
temp = get_temp()
109+
draw_text(draw, 0, 0, "Temp")
110+
draw_text(draw, margin_x_figure, 0, "%s'C" % (format_percent(temp)))
111+
112+
cpu = get_cpu()
113+
draw_text(draw, 0, 1, "CPU")
114+
if cpu < 100:
115+
draw_text(draw, margin_x_figure, 1, "%s %%" % (format_percent(cpu)))
116+
draw_bar(draw, 1, cpu)
117+
else:
118+
draw_bar_full(draw, 1)
119+
120+
mem = get_mem()
121+
draw_text(draw, 0, 2, "Mem")
122+
if mem < 100:
123+
draw_text(draw, margin_x_figure, 2, "%s %%" % (format_percent(mem)))
124+
draw_bar(draw, 2, mem)
125+
else:
126+
draw_bar_full(draw, 2)
127+
128+
disk = get_disk_usage()
129+
draw_text(draw, 0, 3, "Disk")
130+
if disk < 100:
131+
draw_text(draw, margin_x_figure, 3, "%s %%" % (format_percent(disk)))
132+
draw_bar(draw, 3, disk)
133+
else:
134+
draw_bar_full(draw, 3)
135+
136+
if datetime.now().second % (toggle_interval_seconds * 2) < toggle_interval_seconds:
137+
draw_text(draw, 0, 4, get_uptime())
138+
else:
139+
draw_text(draw, 0, 4, get_ip(network_interface_name))
140+
141+
142+
font_size = 12
143+
font_size_full = 10
144+
margin_y_line = [0, 13, 25, 38, 51]
145+
margin_x_figure = 78
146+
margin_x_bar = 31
147+
bar_width = 52
148+
bar_width_full = 95
149+
bar_height = 8
150+
bar_margin_top = 3
151+
toggle_interval_seconds = 4
152+
153+
154+
# None : find suitable IPv4 address among all network interfaces
155+
# or specify the desired interface name as string.
156+
network_interface_name = None
157+
158+
159+
device = get_device()
160+
font_default = ImageFont.truetype(str(Path(__file__).resolve().parent.joinpath("fonts", "DejaVuSansMono.ttf")), font_size)
161+
font_full = ImageFont.truetype(str(Path(__file__).resolve().parent.joinpath("fonts", "DejaVuSansMono.ttf")), font_size_full)
162+
163+
164+
while True:
165+
stats(device)
166+
time.sleep(0.5)

0 commit comments

Comments
 (0)