Raspberry Pi: CPU-Auslastung als Diagramm auf OLED-Display

Im vorherigen Beitrag haben wir uns ein paar Grundlagen zum 0,96'' OLED-Display mit 128x64 Pixeln angeeignet und hier gehen wir ein Stück weiter: Wir lesen die CPU-Auslastung vom Raspberry Pi aus und stellen diese als Diagramm auf dem Display dar. Die Skalierung des Diagramms soll sich dabei abhängig von den dargestellten Werten automatisch anpassen. Zusätzlich wird der aktuelle Wert als Text angezeigt.

Raspberry Pi: CPU-Auslastung im Diagramm

Raspberry Pi: CPU-Auslastung im Diagramm

Benötigte Bibliotheken

Um an die benötigten Informationen zu gelangen, nutzen wir psutil – eine Bibliothek, mit der man Systemwerte auslesen kann. Zur Installation benötigen wir PIP. Wer das nicht hat, muss das vorher noch installieren:

$ sudo apt-get install python-pip

Dann machen wir direkt mit dem Paket weiter:

$ sudo pip install psutil

Wir lesen mit psutil nur die CPU-Auslastung aus, die Bibliothek kann aber noch mehr liefern. Einen genauen Überblick darüber könnt ihr euch hier verschaffen.

Der Code

Wer den vorherigen Beitrag aufmerksam gelesen hat, wird mit dem Code schnell zurecht kommen. Daher gehe ich nur auf das Auslesen der Werte und den Aufbau des Diagramms ein.

#!/usr/bin/python
# coding=utf-8

# Einbindung der notwendigen Grundbibliotheken
import os, sys, time

# Einbindung 0,96 Zoll OLED Display 128x64 Pixel
from lib_oled96 import ssd1306
from smbus import SMBus
from PIL import ImageFont

# Einbindung von Bibliotheken für die gewünschten Funktionen
import psutil

# ----------------------------------------------------------------------------
#  Variablen anlegen
# ----------------------------------------------------------------------------
cpuList = []
scale = 100

# ----------------------------------------------------------------------------
#  Display einrichten
# ----------------------------------------------------------------------------

i2cbus = SMBus(1)		# 0 = Raspberry Pi 1, 1 = Raspberry Pi > 1
oled = ssd1306(i2cbus)

# Ein paar Abkürzungen, um den Code zu entschlacken
draw = oled.canvas

# Display zum Start löschen
oled.cls()
oled.display()

# Schriftarten festlegen
FreeSans08 = ImageFont.truetype('FreeSans.ttf', 8)
FreeSans11 = ImageFont.truetype('FreeSans.ttf', 11)

# ----------------------------------------------------------------------------
#  Hauptprogramm
# ----------------------------------------------------------------------------

while True:
    try:
        # Inhalt des Displays bei jedem Durchlauf neu aufbauen
        draw.rectangle((0,0, oled.width, oled.height), outline=0, fill=0)
    
        # CPU-Auslastung auslesen
        cpu_val = psutil.cpu_percent(interval=1)

        # Momentanwert anzeigen
        draw.text((0, 0), "CPU: " + str(cpu_val) + " %", font=FreeSans11, fill=255)

        # Werte für Diagramm speichern und Länge auf 110 Einträge begrenzen
        cpuList.append(cpu_val)
        if len(cpuList) > 110:
            cpuList.pop(0)

        # Skalierung des Diagramms
        max_val = max(cpuList)

        if max_val <= 100.0:
            scale = 100
        if max_val <= 90.0:
            scale = 90
        if max_val <= 80.0:
            scale = 80
        if max_val <= 70.0:
            scale = 70
        if max_val <= 60.0:
            scale = 60
        if max_val <= 50.0:
            scale = 50
        if max_val <= 40.0:
            scale = 40	
        if max_val <= 30.0:
            scale = 30
        if max_val <= 20.0:
            scale = 20
        if max_val <= 10.0:
            scale = 10

        # Achsen zeichnen
        draw.line((12, 60, oled.width, 60), fill=255)	# X
        draw.line((15, 20, 15, 63), fill=255)			# Y
        draw.line((12, 20, 15, 20), fill=255)			# oberster Skalenwert

        # obersten Y-Wert beschriften
        draw.text((0, 16), str(scale), font=FreeSans08, fill=255)

        # X-Koordinate für ersten Wert festlegen
        X = 17

        # Diagrammwerte zeichnen
        for n in range(len(cpuList)):
            Y = float(40 / float(scale) * cpuList[n])
            draw.line((X, 58, X, 58-Y), fill=255)
            X = X + 1

        # Daten am Display ausgeben
        oled.display()

        # Ausgabe der aktuellen Werte auf der Kommandozeile zum Debuggen
        #print str(cpu_val) + "     |     " + str(max_val) + "     |     " + str(scale) + "     |     " + str(40.0 / float(scale) * float(cpu_val))

    except KeyboardInterrupt:
        oled.cls()
        oled.display()
        sys.exit(0)

Funktionsweise

Grob kann man das Ganze so beschreiben:

Das Display kann, durch seine Breite vorgegeben 110 Einzelwerte darstellen. Den restlichen Platz benötigen wir für die Y-Achse. Die Einzelwerte reihen wir mit 1px breiten Linien aneinander, bis das Display gefüllt ist. So ergibt sich unser Diagramm.

Momentanwerte speichern

Auf Zeile 18 erzeugen wir eine leere Liste und darunter legen wir den Skalenendwert der Y-Achse auf 100 fest. Die momentane CPU-Auslastung lesen wir mittels psutil auf Zeile 49 aus und stellen diese auf Zeile 52 als Text dar.

Mit dem Code auf den Zeilen 55-57 hängen wir Durchlauf für Durchlauf den aktuellen Momentanwert der CPU-Auslastung an die anfangs noch leere Liste an. Diese baut sich somit immer weiter auf. Sie soll jedoch nicht ewig lang werden, da unser Display ja auch nicht ewig breit ist. In der hier verwendeten Darstellung passen 110 Werte in das Diagramm auf dem Display. Somit muss die Liste mit den CPU-Werten auch nicht länger sein. Wenn der 111. Eintrag angehängt wird, wird der erste aus der Liste entfernt (cpuList.pop(0)), sodass die Liste danach wieder 110 Einträge besitzt.

Y-Achse skalieren

Bei geringer CPU-Last und einer Skalierung der Y-Achse, dass 0…100% angezeigt werden können, würde man die meiste Zeit überhaupt keine Werte im Diagramm sehen. Daher soll die Skalierung der Y-Achse automatisch angepasst und die Darstellung der Einzelwerte optimal auf den zur Verfügung stehenden Platz zugeschnitten werden. Dazu ermitteln wir den größten Wert der Liste (Zeile 60) und runden ihn auf 10er-Stellen auf (Zeilen 62-81). Der ermittelte Wert dient als Faktor für die Höhe der zu zeichnenden Linien.

Diagramm zeichnen

Auf den Zeilen 95 bis 98 iterieren wir über alle Werte in der Liste und zeichnen für jeden Einzelwert eine Linie, deren Länge abhängig vom Skalierungsfaktor ist. Die Berechnung lautet wie folgt:

<Diagrammhöhe> / <Skalierungsfaktor> * <CPU-Auslastung>

Die Diagrammhöhe ergibt sich daraus, wie man den Platz auf dem Display dafür freihält. In meinem Beispiel sind 40 Pixel für die Werte vorgesehen. Den Skalierungsfaktor haben wir im vorherigen Abschnitt betrachtet und die CPU-Auslastung haben wir mit psutil erfasst.

Ein Beispiel: In unserer Liste befinden sich jede Menge bunte Werte zwischen 0 und 19. Der Skalierungsfaktor wird demzufolge 20 sein. Der Wert, der gezeichnet werden soll, beträgt 14. Das führt uns zu folgender Formel:

Linienlänge = 40px / 20 * 14% = 28px

Der Startpunkt der Werte ist auf 58px festgelegt. Von dieser Höhe werden die Linien aufwärts gezeichnet.

Auf Zeile 92 haben wir zuvor noch die X-Koordinate für den ersten Wert festgelegt. Bei jedem Durchlauf der for-Schleife wird X um 1 erhöht, sodass jede weitere Linie um je einen Pixel nach rechts verschoben gezeichnet wird.

Debuggen

Wer die im Diagramm angezeigten Werte mit der Berechnung vergleichen möchte, kann Zeile 104 einkommentieren. Hier werden die aktuelle CPU-Auslastung, der höchste Wert in der Liste, der Skalierungsfaktor und die berechnete Linienlänge ausgegeben.

Raspberry Pi: CPU-Auslastung im Diagramm

Raspberry Pi: CPU-Auslastung im Diagramm

Sebastian

11 Comments

  1. Avatar
    Antworten Blackbeard

    Hallo Sebastian,
    Wie kann ich das Script für die CPU-Auslastung mit dem Systemstart ausführen lassen? Meine bevorzugte Lösung wäre mit nem Eintrag in der rc.local. Irgendwie bekomme ich es aber nicht hin. Mit CRON-Jobs kenne ich mich nicht aus. Kannst du mir helfen?

    • Sebastian
      Antworten Sebastian

      Was hast du denn bisher probiert?
      Achte bei rc.local darauf, dass du das Script mit vollem Pfad eintragen musst und ans Ende ein & gehört.

      • Avatar
        Antworten Blackbeard

        Naja, ich habe dein Script kopiert und Rechte mit „chmod 777“ vergeben.
        Wenn ich das Script per ssh im Terminal ausführe, klappt alles. Wenn ich es in die rc.local eintrage „/home/pi/lib_oled96/CPU-Auslastung-Monitor.py &“ tut sich nix. Selbstverständlich habe ich die Zeile von dem „exit 0“ stehen.

        • Sebastian
          Antworten Sebastian

          Das klingt eigentlich schon ganz gut. Bist du sicher, dass der Pfad korrekt ist?
          Vielleicht könntest du auch Python ganz explizit inklusive Pfadangabe aufrufen. Bei Cron zum Beispiel würde der Scriptaufruf so auch nicht funktionieren.

          • Avatar
            Antworten Blackbeard

            Hallo Sebastian,
            dank deiner Hilfe habe ich den Fehler gefunden und behoben. 🙂
            Das Problem wurde verursacht, dass die im Script verwendete Schrift fehlte. Da ich Raspbian lite verwende und keine grafische Oberfläche installiert ist, sind folglich auch keine Schriften installiert.
            1. Also legt man einfach mit mkdir /usr/share/fonts/truetype die fehlenden Ordner an.
            2. Hier bekommt man die verwendete Schrift: https://github.com/opensourcedesign/fonts/blob/master/gnu-freefont_freesans/FreeSans.ttf
            3. Die Datei kopiert man in den vorher erstellten Ordner „truetype“.
            4. Trägt man nun den Pfad in die rc.local ein, wird das Script beim Start des Raspberry ordentlich ausgeführt und der Monitor zeigt die gewünschte CPU-Auslastung an. 🙂

  2. Avatar
    Antworten Ferdi

    Hallo Sebastian,
    erstmal großes Lob für die Superbeschreibung mit den Status LEDs und dem kleinen Monitor
    Für mich ist das alles ziemlich neu mit Linux und Raspberry etc. Nach längerem reinfuchsen,
    habe ich die 3 Status LED, die auch das tun, was sie sollen. Dann habe ich mich an das kleine Display gemacht,
    eigentlich klappt alles, wenn ich das Python Programm manuell aufrufe klappt alles. Wenn ich das Programm genauso in die rc.local schreibe, wie das Python Programm für die LED passiert nix, das Display bleibt dunkel.
    Das Beispielprogramm mit „Hallo Welt!“ klappt allerdings, das wird beim starten geladen.
    Vielleicht weißt du, waran das liegen kann?
    Vielen Dank

    • Sebastian
      Antworten Sebastian

      Hi Ferdi,
      Im log wird sicherlich ein Fehler angezeigt, das solltest du dir einfach mal anschauen.
      Typische Fehler sind aber:
      – Pfad des Aufrufes nicht korrekt
      – Zugriffsrechte reichen nicht
      – Falscher Benutzer führt das Script aus

      • Avatar
        Antworten Ferdi

        Hallo, danke für den Gedankenanstoß, nun hat es geklappt komischerweise fehlte die Schriftart anscheinend beim Autostart, obwohl beim nomalen ausführen keine Probleme da waren…
        Nachdem die Schrift installiert war, klappt nun alles
        Danke

  3. Avatar
    Antworten Deniz

    Hallo Sebastian,
    danke für die tollen Tutorials!

    Bei mir funktioniert das Skript so weit. Was mich etwas stört ist, dass das Display bei jedem „Refresh“ kurz aus und wieder angeht. Ist das normal? Oder bleibt das Display bei dir kontinuierlich an, ohne zu blinken?
    Außerdem schaltet sich das Display nach einiger Zeit ab (habe sie nicht gemessen, schätze so 1 std.).

    Was mir in den Sinn kam, woran es liegen könnte:
    – RPi kriegt nicht genug Strom (sehr unwahrscheinlich, Netzteil ist offiziell und neu, SSH funktioniert normal)
    – OLED Display kriegt nicht genug Strom (Jumperkabel austauschen?)
    – Runtime-Fehler (CPU ist mit „while true“ überfordert, python (oder andere software) begrenzt die wiederholfrequenz auf 1Hz)

    Vielen Dank im Voraus 🙂

    • Sebastian
      Antworten Sebastian

      Hi Deniz,

      Es ist ja nun schon ein paar Tage her, dass ich diesen Beitrag geschrieben habe. Ich kann mich erinnern, dass das Display anfangs bei mir auch „geflackert“ hat, wenn der Inhalt neugezeichnet wird, aber das hatte sich dann irgendwann mal von alleine gegeben. Sicherlich nicht einfach so, sondern durch Veränderungen am Code, aber was das genau war, weiß ich nicht mehr.

      Dass das Display nach einer Weile von alleine aus geht, hatte ich jedoch nie. Der Code lief bei mir Tage lang, als das alles auf dem Schreibtisch lag, ohne irgendwelche Ausfälle. Allerdings muss ich zugeben, dass dem while True-Abschnitt ein time.sleep(0.1) ganz gut stehen würde.

      Für mich war das ganze ohnehin nur Spaß, ich benutze das Display jetzt ganz anders. Insofern hab ich gerade keine Idee, was ich dir raten soll. Allerdings ist es ja auch nicht ausgeschlossen, dass es auch Unterschiede in der Qualität der Displays gibt.

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.