Internet-Speed-Test.

Ein Python PyQt6 Applikation

Zurück

Ich habe mit der Programmiersprache JavaScript bereits einen Internet-Speed-Test gebaut. Siehe hier! Jedoch gibt es sporadisch immer mal wieder Browser CORS-Probleme. Mit dem hier vorgestellten PyQt6-Python-Script gibt es diese Probleme nicht. Weiterhin erledigt eine Python Bibliothek „speedtest-cli“ die eigentliche Messung.

Die Python Bibliothek „speedtest-cli“ wirft standardmäßig nur ein finales Endprodukt aus. Zwischenergebnisse werden nicht geliefert. Das bedeutet, die zwei Anzeigen (Down- & Uploade Gauge) visualisieren keine Zwischenergebnisse!

Ein Internet-Speed-Test misst die Leistung deiner aktuellen Internetverbindung, indem er Testdaten zwischen deinem Gerät und einem speziellen Server sendet und empfängt. Dabei werden vor allem der Download, der Upload und der Ping ermittelt. Wofür so etwas: Die Leistungsfähigkeit deines Anschlusses zu überprüfen.

Download (Herunterladen): Zeigt, wie schnell Daten aus dem Internet auf dein Gerät geladen werden. Wichtig für Webseiten, Videos und Updates.

Upload (Hochladen): Zeigt, wie schnell Daten von deinem Gerät ins Internet gesendet werden. Wichtig für Videogespräche und das Versenden großer Dateien.

Ping (Reaktionszeit): Misst in Millisekunden, wie lange ein Datenpaket braucht, um zum Server und zurück zu reisen. Ein kleiner Wert ist gut für Online-Spiele.

Diese Webseite stellt für den Quellcode Download- und Kopierfunktionen zur Verfügung. Das geht nur mit JavaScript. Wenn du mich für einen unsicheren Kantonisten hältst, verlasse diese Webseite. Alle Informationen und Angebote sind ohne Gewähr!

Python Code
#!/usr/bin/env python3 # ***************************************************************************** # Eine PyQt6 Python Applikation für Internet-Speedtest # Installation der Abhängigkeit: pip install PyQt6 speedtest # Start: Python pyqt6_InternetSpeed.py (c) Hans Busche Datum: 30.07.2026 # ***************************************************************************** import sys, os import speedtest from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout,\ QHBoxLayout, QPushButton, QLabel from PyQt6.QtGui import QPainter, QColor, QFont, QPen, QIcon from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal, QRectF # Verzeichnis dieser Datei, damit das Icon unabhängig vom Startort gefunden wird BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ICON_PATH = os.path.join(BASE_DIR, "Schaefer.ico") #****************************************************************************** # Klasse Analoganzeige (Gauge) fur Down- & Upload #****************************************************************************** class SpeedtestGauge(QWidget): def __init__(self, title="Download", unit="Mbps", is_download=True, parent=None): super().__init__(parent) self.title = title self.unit = unit self.current_value = 0.0 self.target_value = 0.0 self.max_value = 250.0 # Skala bis 250 Mbps if is_download: self.accent_color = QColor(0, 210, 255) self.track_color = QColor(0, 70, 90) else: self.accent_color = QColor(236, 0, 140) self.track_color = QColor(80, 0, 50) self.setMinimumSize(220, 220) self.animation_timer = QTimer(self) self.animation_timer.timeout.connect(self.update_animation) self.animation_timer.start(16) def set_value(self, value): self.target_value = max(0.0, min(self.max_value, float(value))) def update_animation(self): diff = self.target_value - self.current_value if abs(diff) > 0.05: self.current_value += diff * 0.15 self.update() else: self.current_value = self.target_value def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) size = min(self.width(), self.height()) painter.translate(self.width() / 2, self.height() / 2) painter.scale(size / 200.0, size / 200.0) start_angle = -225 * 16 span_angle = 270 * 16 rect = QRectF(-85, -85, 170, 170) pen_track = QPen(self.track_color, 10, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap) painter.setPen(pen_track) painter.drawArc(rect, start_angle, -span_angle) value_ratio = self.current_value / self.max_value active_span = int(span_angle * value_ratio) pen_active = QPen(self.accent_color, 12, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap) painter.setPen(pen_active) painter.drawArc(rect, start_angle, -active_span) painter.setPen(QColor(255, 255, 255)) font_value = QFont("Segoe UI", 24, QFont.Weight.Bold) painter.setFont(font_value) painter.drawText(QRectF(-80, -25, 160, 40), Qt.AlignmentFlag.AlignCenter, f"{self.current_value:.1f}") font_unit = QFont("Segoe UI", 10, QFont.Weight.Normal) painter.setFont(font_unit) painter.setPen(QColor(180, 180, 180)) painter.drawText(QRectF(-80, 15, 160, 20), Qt.AlignmentFlag.AlignCenter, self.unit) font_title = QFont("Segoe UI", 11, QFont.Weight.DemiBold) painter.setFont(font_title) painter.setPen(self.accent_color) painter.drawText(QRectF(-80, -65, 160, 20), Qt.AlignmentFlag.AlignCenter, self.title.upper()) #****************************************************************************** # Der Hintergrund - Thread, damit das Programm nicht einfriert #****************************************************************************** class SpeedtestWorker(QThread): status_signal = pyqtSignal(str) ping_signal = pyqtSignal(float) # Neues Signal für Ping download_signal = pyqtSignal(float) upload_signal = pyqtSignal(float) finished_signal = pyqtSignal() def run(self): try: self.status_signal.emit("Suche besten Server...") st = speedtest.Speedtest() best_server = st.get_best_server() # Ping aus den Serverdetails auslesen (wird in ms geliefert) ping_ms = float(best_server.get("latency", 0.0)) self.ping_signal.emit(ping_ms) # 1. Download-Test self.status_signal.emit("Teste Download-Geschwindigkeit...") download_speed = st.download() / 1_000_000 self.download_signal.emit(download_speed) # 2. Upload-Test self.status_signal.emit("Teste Upload-Geschwindigkeit...") upload_speed = st.upload() / 1_000_000 self.upload_signal.emit(upload_speed) self.status_signal.emit("Test erfolgreich abgeschlossen!") except Exception as e: self.status_signal.emit(f"Fehler: {str(e)}") finally: self.finished_signal.emit() #****************************************************************************** # Das Hauptfenster #****************************************************************************** class SpeedtestMainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("PyQt6-Internet-Speed-Test") self.resize(550, 450) # Icon if os.path.exists(ICON_PATH): self.setWindowIcon(QIcon(ICON_PATH)) self.setStyleSheet("background-color: #121214; color: white;") main_layout = QVBoxLayout(self) # Status-Label ganz oben self.status_label = QLabel("Bereit für den Test") self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.status_label.setStyleSheet("font-size: 14px; color: \ #aaa; margin-top: 15px;") main_layout.addWidget(self.status_label) # Ping-Anzeige (Dezent zentriert unter dem Status) self.ping_label = QLabel("Ping: -- ms") self.ping_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.ping_label.setStyleSheet(""" font-size: 16px; font-weight: bold; color: #00FF66; background-color: #1A1A1E; padding: 6px 15px; border-radius: 12px; margin: 10px auto; """) main_layout.addWidget(self.ping_label) # Layout für die Gauges gauge_layout = QHBoxLayout() self.download_gauge = SpeedtestGauge(title="Download", is_download=True) self.upload_gauge = SpeedtestGauge(title="Upload", is_download=False) gauge_layout.addWidget(self.download_gauge) gauge_layout.addWidget(self.upload_gauge) main_layout.addLayout(gauge_layout) # Start-Button self.start_btn = QPushButton("Speedtest starten") self.start_btn.setStyleSheet(""" QPushButton { background-color: #1f1f23; border: 2px solid #333; border-radius: 6px; padding: 12px; font-weight: bold; font-size: 13px; margin-top: 10px; } QPushButton:hover { background-color: #2a2a30; border-color: #555; } QPushButton:disabled { background-color: #101012; color: #555; border-color: #222; } """) self.start_btn.clicked.connect(self.start_speedtest) main_layout.addWidget(self.start_btn) self.worker = None def start_speedtest(self): self.start_btn.setEnabled(False) self.ping_label.setText("Ping: ... ms") self.download_gauge.set_value(0) self.upload_gauge.set_value(0) self.worker = SpeedtestWorker() # Signale verbinden (Neu: ping_signal) self.worker.status_signal.connect(self.status_label.setText) self.worker.ping_signal.connect(self.update_ping_display) self.worker.download_signal.connect(self.download_gauge.set_value) self.worker.upload_signal.connect(self.upload_gauge.set_value) self.worker.finished_signal.connect(self.test_finished) self.worker.start() def update_ping_display(self, ping_value): """Formatiert und zeigt den empfangenen Ping-Wert an.""" self.ping_label.setText(f"Ping: {ping_value:.0f} ms") def test_finished(self): self.start_btn.setEnabled(True) self.worker.deleteLater() if __name__ == '__main__': app = QApplication(sys.argv) window = SpeedtestMainWindow() window.show() sys.exit(app.exec()) #****************************************************************************** # Ende #******************************************************************************


1️⃣ Die Klasse „SpeedtestGauge“, Custom-Widget, die Anzeige, Zeile 22 bis 96:
Dieses Widget ist für die rein optische Darstellung und die flüssige Bewegung der Nadel zuständig. Keine starre Sprunganzeige. „update_animation“. Wenn ein neuer Wert über set_value() reinkommt, springt die Anzeige nicht sofort dorthin. Stattdessen berechnet ein QTimer 60-mal pro Sekunde (alle 16 Millisekunden) die Differenz zwischen dem aktuellen Zeigerstand (self.current_value) und dem Zielwert (self.target_value). Sie nähert sich mathematisch gedämpft an, was diese typische, geschmeidige "Tacho-Bewegung" erzeugt.

Das Zeichnen (paintEvent):
Sobald sich der Wert ändert, wird das Widget neu gezeichnet. Mit QPainter verlagern wir den Nullpunkt in die Mitte des Widgets (translate). Zuerst wird ein grauer/dunkler Hintergrund-Bogen gezeichnet. Darüber wird ein farbiger Bogen (drawArc) gelegt, dessen Länge exakt proportional zum aktuellen Wert im Verhältnis zum Maximalwert (self.max_value) berechnet wird. Am Ende wird der Text (die Zahl und die Einheit) mathematisch exakt im Zentrum platziert.

2️⃣ Die Klasse „SpeedtestWorker“, der Hintergrund-Arbeiter, die Logik, Zeile 101 bis 132:
Das ist das Herzstück für die Performance. Ein Internet-Speedtest blockiert das Skript für mehrere Sekunden, da große Datenmengen hoch- und runtergeladen werden müssen. QThread (Multithreading). Würde man den Speedtest im selben Thread wie das Fenster ausführen, würde das Betriebssystem melden: "Anwendung reagiert nicht mehr", das Fenster würde einfrieren und die Animationen der Tachos würden stoppen. Durch QThread läuft der Test komplett isoliert im Hintergrund.
Sichere Kommunikation via pyqtSignal: Ein Hintergrund-Thread darf in Qt niemals direkt die Benutzeroberfläche verändern (das führt unweigerlich zu Abstürzen). Deshalb feuert der Thread stattdessen "Signale" (wie kleine Funksprüche) in den Raum, sobald er neue Daten hat:
ping_signal.emit(ping_ms) sendet die Latenz.
download_signal.emit(download_speed) sendet das Ergebnis des Downloads.
status_signal.emit(...) sendet den aktuellen Textschritt für den Benutzer.


3️⃣ Die Klasse „SpeedtestMainWindow“, das Hauptfenster, der Dirigent, Zeile 137 bis 227:
Das Hauptfenster baut die Oberfläche ohne Designer auf und verbindet die Logik des Hintergrund-Threads mit den beiden Tacho-Anzeigen. Layout-Management: Über QVBoxLayout (vertikal) und QHBoxLayout (horizontal) werden die Elemente (Status-Text, die grüne Ping-Pille, die zwei Gauges und der Button) sauber unter- und nebeneinander angeordnet. Sie passen sich automatisch an, wenn du das Fenster größer oder kleiner ziehst.
Die Verbindung (Das "Verkabeln"): In der Methode start_speedtest() wird der Hintergrund-Thread erzeugt. Nun werden die Funksprüche (Signale) des Threads mit den Methoden des Hauptfensters verknüpft: self.worker.download_signal.connect(self.download_gauge.set_value).


PyQt6 Internet Speed
Zurück