Einstieg in Python, Teil 11
Virtuelle Instrumente
In einer zunehmend digitalisierten Welt spielen virtuelle Instrumente eine immer wichtigere Rolle. In Forschung, Lehre und industriellen Anwendungen sind sie praktisch überall im Einsatz. Anstelle teurer physikalischer Messgeräte ermöglichen es virtuelle Instrumente, Signale zu simulieren, Daten zu erfassen, zu analysieren und visuell darzustellen – alles direkt auf einem Computer oder sogar auf einem kleinen System wie dem Raspberry Pi. Besonders attraktiv wird dieses Konzept durch die Programmiersprache Python, die durch ihre einfache Syntax und umfangreiche Bibliotheken ideale Voraussetzungen für die Entwicklung solcher Softwarelösungen bietet.
Ob Oszilloskope, Spektrumanalysatoren oder Temperaturmonitore – mit Python lassen sich vielseitige virtuelle Instrumente erstellen, die nicht nur kostengünstig, sondern auch flexibel anpassbar sind. Dieser Artikel gibt einen praxisnahen Einstieg in die Welt der virtuellen Instrumente mit Python: von der grundlegenden Idee über geeignete Bibliotheken wie Tkinter bis hin zu konkreten Umsetzungsbeispielen.
Analoge Eingangssignale erfassen
Als leistungsfähiger Einplatinencomputer eignet sich der Raspberry Pi hervorragend für Mess- und Steuerungsaufgaben. Eine seiner wenigen Einschränkungen: Er verfügt über keine integrierten analogen Eingänge. Für Projekte, die analoge Signale – etwa von Sensoren, Potentiometern oder analogen Spannungsquellen – erfassen sollen, ist daher ein externer Analog-Digital-Wandler (ADC) notwendig.
Ein besonders kompakter und leicht zu integrierender ADC ist der MCP3002 von Microchip. Dieser 10-Bit-Konverter kommuniziert über die SPI-Schnittstelle und bietet zwei analoge Eingangskanäle. Damit eignet er sich ideal für einfache Messaufgaben wie Spannungsmessung, Sensoranbindung oder Signalanalyse.
In Kombination mit Python und der spidev-Bibliothek lässt sich der MCP3002 schnell in Betrieb nehmen. Die Messdaten können anschließend für weitere Anwendungen genutzt werden – etwa zur Visualisierung in einem virtuellen Oszilloskop, zur Datenaufzeichnung oder zur Steuerung von Aktoren.
Im Folgenden wird kurz erläutert, wie der ADC am Raspberry Pi eingesetzt werden kann. Weitere Details dazu wurden bereits im Artikel „Erfassung analoger Werte“ in Teil 5 dieser Reihe vorgestellt und können dort bei Bedarf nachgeschlagen werden.
Der MCP3002 ermöglicht mit seinen zwei Eingangskanälen die präzise Erfassung analoger Spannungen – z. B. von Temperatur-, Licht- oder Positionssensoren. Dank seines kleinen Gehäuses, niedrigen Stromverbrauchs und der einfachen Ansteuerung ist er ideal für kompakte und energieeffiziente Messsysteme. Die folgende Tabelle fasst die wichtigsten Daten des Bausteins zusammen:
| Auflösung | 10 Bit (Wertebereich: 0–1023) |
| Eingangskanäle | 2 (CH0 und CH1) |
| Schnittstelle | SPI (bis zu 3,6 MHz Taktfrequenz) |
| Versorgungsspannung | 2,7–5,5 V |
| Abtastrate | bis ca. 200 ksps (kilo-samples per second) |
| Typischer Stromverbrauch | < 1 mA aktiv, < 2 µA im Stand-by |
In Bild 1 und Bild 2 wird gezeigt, wie der Baustein mit dem Raspberry Pi verbunden wird.


Die folgende Tabelle zeigt die Verbindungen im Überblick:
| MCP3002 am Raspberry Pi | ||
| MCP3002 | Raspberry Pi | |
| CS | Pin 1 | CS0 – GPIO 10 |
| CH0 | Pin 2 | Analog Input |
| CH1 | Pin 3 | Analog Input |
| VSS | Pin 4 | GND |
| DIN | Pin 5 | MOSI – GPIO 12 |
| DOUT | Pin 6 | MISO – GPIO 13 |
| CLK | Pin 7 | SCLK – GPIO 14 |
| VDD | Pin 8 | 3V3 |
Das folgende Programm liest den MCP3002 aus und stellt die ADC-Counts sowie die zugehörigen Spannungswerte auf der Konsole dar (MCP3002_tst.py):
import spidev
import time
# SPI initialisieren
spi = spidev.SpiDev()
spi.open(0, 0) # Bus 0, Device 0 (CE0)
spi.max_speed_hz = 1000000 # 1 MHz
# Referenzspannung (V_REF = 3.3V)
V_REF = 3.3
# Funktion zum Auslesen des MCP3002 (Kanal 0 oder 1)
def read_mcp3002(channel):
if channel not in [0, 1]:
raise ValueError("Kanal muss 0 oder 1 sein")
# Startbit (1), Single-Ended (1), Kanal (0/1), MSBF (1)
data = spi.xfer2([0b01101000 | (channel << 4), 0])
# Ergebnis: 10-Bit-Wert aus den empfangenen Bytes
adc_value = ((data[0] & 0x03) << 8) | data[1]
return adc_value
# Spannung berechnen
def calculate_voltage(adc_value):
return (adc_value / 1023) * V_REF
# Hauptprogramm
try:
while True:
# Lies ADC-Wert von Kanal 0
adc_value = read_mcp3002(0)
# Berechne Spannung
voltage = calculate_voltage(adc_value)
# Ausgabe in der Konsole
print(f"ADC-Wert: {adc_value}, Spannung: {voltage:.2f} V")
time.sleep(0.1) # Aktualisiere alle 100 ms
except KeyboardInterrupt:
print("\nProgramm beendet.")
finally:
spi.close()
Durch Anschluss eines zweiten Potentiometers (an Ch1, Pin3) können auch beide Kanäle ausgelesen werden (MCP3002_dual.py):
import spidev
import time
# SPI initialisieren
spi = spidev.SpiDev()
spi.open(0, 0) # Bus 0, Device 0 (CE0)
spi.max_speed_hz = 1000000 # 1 MHz
# Referenzspannung (V_REF = 3.3V)
V_REF = 3.3
# Funktion zum Auslesen des MCP3002 (Kanal 0 oder 1)
def read_mcp3002(channel):
if channel not in [0, 1]:
raise ValueError("Kanal muss 0 oder 1 sein")
# Startbit (1), Single-Ended (1), Kanal (0/1), MSBF (1)
data = spi.xfer2([0b01101000 | (channel << 4), 0])
# Ergebnis: 10-Bit-Wert aus den empfangenen Bytes
adc_value = ((data[0] & 0x03) << 8) | data[1]
return adc_value
# Spannung berechnen
def calculate_voltage(adc_value):
return (adc_value / 1023) * V_REF
# Hauptprogramm
try:
while True:
for channel in [0, 1]:
adc_value = read_mcp3002(channel)
voltage = calculate_voltage(adc_value)
print(f"Kanal {channel}: ADC-Wert = {adc_value}, Spannung = {voltage:.2f} V")
print("-" * 40)
time.sleep(0.5) # Aktualisiere alle 500 ms
except KeyboardInterrupt:
print("\nProgramm beendet.")
finally:
spi.close()
Die Ausgabe auf die Konsole sieht dann beispielsweise so aus (Bild 3):

Grafische Messwertdarstellung
Nachdem nun analoge Messwerte erfasst werden können, sollen diese im nächsten Schritt auf virtuellen Instrumenten dargestellt werden. Eine der einfachsten Möglichkeiten hierfür sind sogenannte Bargraphanzeigen, auch als Balkendiagramme bekannt. Trotz ihrer Einfachheit bieten Balkendiagramme eine Reihe von Vorteilen, insbesondere wenn es darum geht, Daten klar und leicht verständlich darzustellen. Die Hauptvorteile dieser Darstellungsvarianten sind:
- Übersichtlichkeit: Sie machen Unterschiede zwischen Daten schnell sichtbar, da die Länge der Balken direkt vergleichbar ist
- Einfache Interpretation: Selbst ohne tiefgehende Kenntnisse in Statistik kann man die Informationen schnell erfassen
- Flexibilität: Sie eignen sich sowohl für absolute Werte als auch für Verhältnisse
- Visuelle Wirkung: Farben und Anordnung können genutzt werden, um Muster oder Trends hervorzuheben
- Geringer Platzverbrauch: Sie können sowohl horizontal als auch vertikal gestaltet werden, je nachdem, was für die Darstellung am sinnvollsten ist
Das folgende Programm stellt die beiden ADC-Kanäle in Form von vertikalen Balken dar (MCP3002_dualBargraph.py). Die bereits bekannten Programmteile wurden im Folgenden weggelassen; der vollständige Code findet sich im Downloadpaket.
import spidev
import tkinter as tk
[...]
root = tk.Tk()
root.title("MCP3002 Bargraphanzeige")
canvas_width = 300; canvas_height = 200; bar_width = 100; bar_spacing = 110
canvas = tk.Canvas(root, width=canvas_width, height=canvas_height, bg='white')
canvas.pack()
bar0 = canvas.create_rectangle(50, canvas_height, 50 + bar_width, canvas_height, fill="green")
bar1 = canvas.create_rectangle(50 + bar_spacing, canvas_height, 50 + bar_spacing + bar_width, canvas_height, fill="blue")
label0 = canvas.create_text(100, canvas_height - 5, text="CH0", font=("Arial", 10))
label1 = canvas.create_text(100 + bar_spacing, canvas_height - 5, text="CH1", font=("Arial", 10))
value_text0 = canvas.create_text(100, 10, text="", font=("Arial", 12, "bold"))
value_text1 = canvas.create_text(100 + bar_spacing, 10, text="", font=("Arial", 12, "bold"))
def update_bars():
value0 = read_mcp3002(0); value1 = read_mcp3002(1)
height0 = int((value0 / max_adc) * (canvas_height - 40))
height1 = int((value1 / max_adc) * (canvas_height - 40))
canvas.coords(bar0, 50, canvas_height - height0 - 20, 50 + bar_width, canvas_height - 20)
canvas.coords(bar1, 50 + bar_spacing, canvas_height - height1 - 20, 50 + bar_spacing + bar_width, canvas_height - 20)
voltage0 = value0 * V_REF / max_adc; voltage1 = value1 * V_REF / max_adc
canvas.coords(value_text0, 100, canvas_height - height0 - 30)
canvas.itemconfig(value_text0, text=f"{voltage0:.2f} V")
canvas.coords(value_text1, 100 + bar_spacing, canvas_height - height1 - 30)
canvas.itemconfig(value_text1, text=f"{voltage1:.2f} V")
root.after(200, update_bars)
update_bars()
root.mainloop()
spi.close()
Nach dem Start des Programms erscheinen die aktuellen Spannungswerte beider ADC-Kanäle in jeweils einer eigenen Balkendarstellung (Bild 4). Über den Balken werden zusätzlich die exakten Werte in numerischer Form ausgegeben.

Virtuelle Instrumente in allen Formen und Farben
Rundinstrumente, wie sie häufig in Fahrzeugen, Maschinen und Uhren verwendet werden, bieten gegenüber Bargraphanzeigen einige praktische Vorteile:
- Schnelle Ablesbarkeit: Der Zeiger zeigt intuitiv auf den Wert, wodurch Informationen leicht erfasst werden können
- Gute Wahrnehmung bei Bewegung: Besonders in Autos oder Flugzeugen ist es einfacher, Veränderungen eines Zeigers zu erfassen als kleine digitale Zahlen
- Ästhetik und Design: Runde Anzeigen wirken oft klassisch und hochwertig, weshalb sie in vielen Premium-Produkten eingesetzt werden
- Einfache Skalierung: Die Kreisform ermöglicht eine natürliche Verteilung von Skalenwerten, die in einem Blickfeld erfassbar sind
Auch Rundinstrumente (Bild 5) können relativ einfach in der virtuellen Welt dargestellt werden, wenngleich ihre Programmierung etwas aufwendiger ist als die Darstellung von Balkendiagrammen. Das folgende Programm (MCP3002_VirtualAnalog.py) zeigt die am ADC-Kanal 0 anliegende Spannung in einem Rundinstrument an:

import spidev, time, math, time
import tkinter as tk
from threading import Thread
[...]
class AnalogInstrument(tk.Tk):
def __init__(self):
super().__init__()
self.title("MCP3002 Voltmeter")
self.geometry("400x450")
self.canvas = tk.Canvas(self, width=400, height=450, bg="#000000")
self.canvas.pack()
self.canvas.create_oval(50, 50, 350, 350, outline="#00008B", fill="#00008B", width=2)
for i in range(0, 271, 27):
angle = math.radians(i - 135 + 270)
x1 = 200 + math.cos(angle) * 120
y1 = 200 + math.sin(angle) * 120
x2 = 200 + math.cos(angle) * 140
y2 = 200 + math.sin(angle) * 140
self.canvas.create_line(x1, y1, x2, y2, fill="#00FF00")
label_x = 200 + math.cos(angle) * 170
label_y = 200 + math.sin(angle) * 170
label_value = round(i * 5.0 / 270, 2) # Spannung berechnen
self.canvas.create_text(label_x, label_y, text=str(label_value), font=("Arial", 16), fill="#00FF00")
self.pointer = self.canvas.create_line(200, 200, 200, 80, fill="#FF4500", width=4)
self.voltage_label = self.canvas.create_text(200, 400, text="Spannung: 0 V", font=("Arial", 20), fill="#00FF00")
self.running = True
self.thread = Thread(target=self.update_instrument)
self.thread.daemon = True
self.thread.start()
def update_instrument(self):
while self.running:
try:
voltage = read_mcp3002(0)
angle = (voltage / 5.0) * 270 - 135 + 270
angle_rad = math.radians(angle)
x = 200 + math.cos(angle_rad) * 110
y = 200 + math.sin(angle_rad) * 110
self.canvas.coords(self.pointer, 200, 200, x, y)
self.canvas.itemconfig(self.voltage_label, text=f"Spannung: {voltage:.2f} V")
time.sleep(0.1)
except Exception as e:
print(f"Fehler: {e}")
break
def destroy(self):
self.running = False
spi.close()
super().destroy()
# Hauptprogramm
app = AnalogInstrument()
app.mainloop()
Das Programm emuliert ein einfaches analoges Voltmeter, das die Messwerte eines MCP3002-AD-Wandlers grafisch als Rundinstrumenten-Anzeige darstellt.
Die Klasse AnalogInstrument erstellt eine grafische Oberfläche mit Tkinter. Das Voltmeter besteht dabei aus den folgenden Elementen:
- Ziffernblatt mit Skala (0–5 V)
- Zeiger
- Numerische Anzeige der Spannung
Für eine flüssige Darstellung wird ein eigener Thread (self.thread) gestartet, um die Spannung kontinuierlich zu messen und die Anzeige zu aktualisieren. Der Zeiger wird dann entsprechend der gemessenen Spannung dargestellt. Die Spannung wird in einen Winkel (0–270°) umgerechnet, und die Koordinaten des Zeigers werden mit self.canvas.coords() berechnet. Das Programm läuft in einer Endlosschleife, bis die GUI geschlossen wird. Danach wird die SPI-Schnittstelle sauber beendet.
Man kann sogar noch einen Schritt weiter gehen und ein klassisches Analoginstrument virtualisieren. Bild 6 zeigt eine entsprechende Darstellung. Da das Programm hierfür bereits relativ umfangreich ist, soll es hier nicht im Detail wiedergegeben und diskutiert werden. Der vollständige Code findet sich aber im Downloadpaket.

Ein besonderer Vorteil bei virtuellen Instrumenten ist, dass der exakte Wert zusätzlich auch digital dargestellt werden kann. Damit werden Ablesefehler nahezu ausgeschlossen.
Wetter- und Klimadaten aus dem Internet
Auch ohne AD-Wandler können virtuelle Instrumente nutzbringend eingesetzt werden. Eine Möglichkeit ist die Verwendung von aktuellen Wetterdaten aus dem Internet. Damit lassen sich individuelle Klimastationen aufbauen, ohne dass aufwendige Sensoren auf der Anwenderseite erforderlich sind.
Die Wetterdaten können von OpenWeatherMap auf den Raspberry Pi geladen werden, indem eine API genutzt wird. Hier sind die grundlegenden Schritte:
- Auf OpenWeatherMap registrieren und einen kostenlosen API-Schlüssel erstellen
- requests installieren, um HTTP-Anfragen zu senden:
pip install requests
Dann können verschiedene Wetterdaten ausgelesen und dargestellt werden (OpenWeatherMap_tst.py)
import requests
# API-Schlüssel und Stadt
API_KEY = "12345678901234567890123456789012"
STADT = "Leer"
URL = f"http://api.openweathermap.org/data/2.5/weather?q={STADT}&appid={API_KEY}&units=metric"
# Wetterdaten abrufen
response = requests.get(URL)
wetterdaten = response.json()
# Daten extrahieren
temperatur = f"{wetterdaten['main']['temp']} °C"
luftfeuchtigkeit = f"{wetterdaten['main']['humidity']} %"
luftdruck = f"{wetterdaten['main']['pressure']} hPa"
windgeschwindigkeit = f"{wetterdaten['wind']['speed']} m/s"
wetterlage = wetterdaten['weather'][0]['description']
# Formatierte Ausgabe
print("="*30)
print(f"{'Parameter':<20}{'Wert':<10}")
print("="*30)
print(f"{'Temperatur':<20}{temperatur:<10}")
print(f"{'Luftfeuchtigkeit':<20}{luftfeuchtigkeit:<10}")
print(f"{'Luftdruck':<20}{luftdruck:<10}")
print(f"{'Windgeschwindigkeit':<20}{windgeschwindigkeit:<10}")
print(f"{'Wetterlage':<20}{wetterlage:<10}")
print("="*30)
Bild 7 zeigt ein beispielhaftes Ergebnis dieser Abfrage.

Im Folgenden soll gezeigt werden, wie diese Daten für einer ansprechenden Wetterstation umgesetzt werden können.
Umfangreiche virtuelle Klimastation
Eine virtuelle Wetterstation auf dem Raspberry Pi bietet zahlreiche praktische Vorteile, die sie zu einer idealen Lösung für private und pädagogische Zwecke machen. Der größte Pluspunkt ist die Kosteneffizienz – im Gegensatz zu teuren professionellen Wetterstationen genügen hier ein preiswerter Raspberry Pi und ein einfaches Display. Auf kostspielige Spezialsensoren kann verzichtet werden, da die Wetterdaten direkt von der OpenWeatherMap API bezogen werden.
Die Station ist platzsparend und mobil einsetzbar, sei es in der Wohnung, im Büro oder in einem Klassenzimmer. Anders als bei analogen Wetterstationen gibt es keine mechanischen Bauteile, die verschleißen könnten, was die Wartung vereinfacht. Updates erfolgen rein softwarebasiert, und Messbereiche lassen sich problemlos per Code anpassen, ohne dass Hardware ausgetauscht werden müsste.
Die Erweiterbarkeit ist ebenfalls ein großer Vorteil: Nutzer können die Anzeige individuell anpassen, zusätzliche Wetterparameter einbinden oder die Visualisierung für verschiedene Displaygrößen skalieren. Zudem lassen sich „Smart Features“ wie
- automatische Datenaktualisierungen,
- historische Datenspeicherung oder
- Warnmeldungen bei Extremwerten
implementieren.
Die Wetterstation (OpenWeatherMap_graph.py) zeigt die aktuellen Wetterdaten und Vorhersagen für einen konfigurierbaren Ort (z. B. Leer, Hamburg oder München) an. Das Programm nutzt die OpenWeatherMap API, um Echtzeit-Wetterinformationen abzurufen und in einer ansprechenden Visualisierung darzustellen. Die Anwendung besteht aus vier Hauptinstrumenten:
- Thermometer mit einem Messbereich von -20 °C bis +40 °C,
- Hygrometer für die Luftfeuchtigkeit von 0 bis 100 %,
- Barometer für den Luftdruck (950–1050 hPa)
- Windrose sowie Bargraphanzeige zur Darstellung von Windrichtung und -geschwindigkeit (0–100 km/h).
Da das Programm recht umfangreich ist, soll es hier nicht vollständig angegeben werden (die komplette Version findet sich im Downloadpaket). Vielmehr sollen exemplarisch einige wichtige Details näher erläutert werden.
Das Dashboard liefert das Grundgerüst für die grafische Anzeige:
class WeatherDashboard(tk.Tk):
def __init__(self):
super().__init__()
self.title("Virtuelle Wetterstation")
self.geometry("1250x800")
self.config(bg=BackroundColor)
main_frame = tk.Frame(self, bg=BackroundColor)
main_frame.pack(expand=True, fill=tk.BOTH, padx=20, pady=20)
left_frame = tk.Frame(main_frame, bg=BackroundColor)
left_frame.grid(row=0, column=0, padx=10, pady=10, sticky="n")
center_frame = tk.Frame(main_frame, bg=BackroundColor)
center_frame.grid(row=0, column=1, padx=10, pady=10, sticky="n")
right_frame = tk.Frame(main_frame, bg=BackroundColor)
right_frame.grid(row=0, column=2, padx=10, pady=10, sticky="n")
# Linke Spalte
self.weather_icon_frame = tk.Frame(left_frame, bg=BackroundColor)
self.weather_icon_frame.pack()
self.weather_icon_label = tk.Label(self.weather_icon_frame, bg=BackroundColor)
self.weather_icon_label.pack()
self.weather_desc_label = tk.Label(left_frame, text="Aktuelles Wetter: -", font=("Arial", 14, "bold"), bg=BackroundColor)
self.weather_desc_label.pack(pady=5)
self.frame_temp = tk.Frame(left_frame, bg=BackroundColor)
self.frame_temp.pack(pady=20)
self.temp_label = tk.Label(self.frame_temp, text="Temperatur: 0°C", font=("Arial", 14, "bold"), bg=BackroundColor)
self.temp_label.pack()
self.canvas_temp = tk.Canvas(self.frame_temp, width=200, height=400, bg=InstrumentBackground, highlightthickness=0)
self.canvas_temp.pack()
# Mittlere Spalte
self.frame_baro = tk.Frame(center_frame, bg=BackroundColor)
self.frame_baro.pack()
self.baro_label = tk.Label(self.frame_baro, text="Luftdruck: 0 hPa", font=("Arial", 14, "bold"), bg=BackroundColor)
self.baro_label.pack()
self.canvas_baro = tk.Canvas(self.frame_baro, width=300, height=300, bg=InstrumentBackground, highlightthickness=0)
self.canvas_baro.pack()
self.frame_wind = tk.Frame(center_frame, bg=BackroundColor)
self.frame_wind.pack(pady=20)
self.wind_label = tk.Label(self.frame_wind, text="Wind: N (0°) - 0 km/h", font=("Arial", 14, "bold"), bg=BackroundColor)
self.wind_label.pack()
self.canvas_wind = tk.Canvas(self.frame_wind, width=300, height=300, bg=InstrumentBackground, highlightthickness=0)
self.canvas_wind.pack()
[...]
Das Thermometer wird als stilisierte Quecksilbersäule angezeigt:
def draw_thermometer(self):
self.canvas_temp.create_rectangle(80, 30, 120, 350, outline="black", width=2)
self.canvas_temp.create_oval(80, 330, 120, 390, outline="black", width=2, fill="red")
for i in range(-20, 41, 5):
y = 350 - ((i + 20) * 5)
self.canvas_temp.create_line(120, y, 140, y, fill="black", width=2)
self.canvas_temp.create_text(150, y, text=f"{i}°C", font=("Arial", 10), anchor="w", fill="blue")
self.mercury = self.canvas_temp.create_rectangle(81, 350, 119, 350, fill="red")
Luftdruckanzeige und Hygrometer bestehen aus einem Rundinstrument:
def draw_hygrometer(self):
self.canvas_humidity.create_oval(50, 50, 350, 350, outline="black", width=1, fill=InstrumentColor)
for i in range(0, 101, 10):
angle = math.radians((i / 100) * 270 - 135 + 270)
x1 = 200 + math.cos(angle) * 120
y1 = 200 + math.sin(angle) * 120
x2 = 200 + math.cos(angle) * 140
y2 = 200 + math.sin(angle) * 140
self.canvas_humidity.create_line(x1, y1, x2, y2, fill="blue", width=2)
label_x = 200 + math.cos(angle) * 100
label_y = 200 + math.sin(angle) * 100
self.canvas_humidity.create_text(label_x, label_y, text=f"{i}%", font=("Arial", 12, "bold"), fill="blue")
self.pointer = self.canvas_humidity.create_line(200, 200, 200, 80, fill="red", width=4)
Die Wettervorhersage wird als fünfzeiliger Text ausgegeben:
def update_forecast(self, forecast_data):
weekdays_de = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
daily_forecasts = {}
for forecast in forecast_data['list']:
date = forecast['dt_txt'].split()[0]
if date not in daily_forecasts:
daily_forecasts[date] = []
daily_forecasts[date].append(forecast)
forecast_dates = sorted(daily_forecasts.keys())[:5]
for i, date in enumerate(forecast_dates):
day_forecast = None
for forecast in daily_forecasts[date]:
if forecast['dt_txt'].endswith("12:00:00"):
day_forecast = forecast
break
if not day_forecast:
day_forecast = daily_forecasts[date][len(daily_forecasts[date])//2]
temp_max = max(f['main']['temp_max'] for f in daily_forecasts[date])
temp_min = min(f['main']['temp_min'] for f in daily_forecasts[date])
description = day_forecast['weather'][0]['description'].capitalize()
weekday_num = time.strptime(date, "%Y-%m-%d").tm_wday
weekday = weekdays_de[weekday_num]
forecast_text = (f"{weekday} ({date[8:10]}.{date[5:7]}): "
f"{description}, {day_forecast['main']['temp']:.1f}°C "
f"(max {temp_max:.1f}°C, min {temp_min:.1f}°C), "
f"Wind: {day_forecast['wind']['speed']*3.6:.1f} km/h")
if i < len(self.forecast_labels):
self.forecast_labels[i].config(text=forecast_text)
Die Vorhersage erfolgt mit deutschen Wochentagen und gibt die erwartete Wetterentwicklung an.
Das Programm nutzt Tkinter für die GUI-Entwicklung und verwendet die Bibliotheken „requests“ für den API-Zugriff, sowie „threading“ für die regelmäßige Aktualisierung der Daten im Hintergrund. Die Aktualisierung erfolgt alle zehn Minuten, bei Fehlern wird ein kürzeres Intervall verwendet.
Besondere Merkmale sind die freie Farbgestaltung, die präzise Skalierung aller Anzeigeinstrumente und die deutschsprachige Oberfläche. Die Windrose verfügt über einen stilisierten Zeiger, während das Barometer eine klare Druckanzeige mit skalierter Anzeige bietet.
Das Programm (Bild 8) startet mit einem Fenster der Größe 1250 x 800 Pixel und ist in drei Spalten unterteilt: Links befinden sich Thermometer und Wettergrafik, in der Mitte Barometer und Windrose, rechts Hygrometer, Windgeschwindigkeit und die fünftägige Vorhersage. Alle Instrumente sind klar beschriftet und zeigen die Werte sowohl grafisch als auch numerisch an.

Ergänzungen und Anregungen
- Wie kann man Stromstärken mit einem virtuellen Instrument anzeigen?
- Kann man die Instrumente nach Bild 5 oder Bild 6 auch mit Autoscaling-Funktionen versehen?
- Könnte man auch reale Sensoren für Luftdruck und -feuchtigkeit auf virtuellen Analoguhr-Instrumenten ausgeben?
- Wie könnten diese Instrumente aussehen?
- Kann man die Instrumente auch durch animierte Grafiken darstellen?
- Wie kann man die Wetterstation um historische Verläufe von Temperatur, Feuchtigkeit oder Luftdruckwerten ergänzen?
Zusammenfassung und Ausblick
In diesem Beitrag wurden die grafischen Darstellungsmöglichkeiten für virtuelle Instrumente ausführlich untersucht. Neben messtechnischen Anwendungen wie Analogvoltmetern wurde dabei auch eine vollständige Klimastation implementiert. In den nächsten Beiträgen soll es hingegen wieder um reale Hardware-Instrumente in Form von Displays und Anzeigeeinheiten gehen. Diese können auf verschiedene Weise mit dem Raspberry Pi angesteuert werden. Auch hier stellt Python wieder eine Reihe nützlicher Hilfsmittel zur Verfügung. Im kommenden Blog-Beitrag soll dabei die Ansteuerung von 7-Segment-Anzeigen im Mittelpunkt stehen.
Material
- Raspberry Pi mit Netzteil
- Breadboard und Jumper-Kabel
- Analog-Digital-Wandler MCP3002
- Kleinteile aus dem Set PAD-PRO-EXSB
Über den Autor
Dr. Günter Spanner ist als Autor zu den Themen Elektronik, Sensortechnik und Mikrocontroller einem weiten Fachpublikum bekannt. Schwerpunkt seiner hauptberuflichen Tätigkeit für verschiedene Großkonzerne wie Siemens und ABB ist die Projektleitung im Bereich Entwicklung und Technologie-Management. Der Dozent für Physik und Elektrotechnik hat zudem zahlreiche Fachartikel und Bücher veröffentlicht sowie Kurse und Lernpakete erstellt.