Herzschrittmacher für den Raspberry Pi – Teil 5: Raspberry Pi Software

Herzschrittmacher Projekt LogoDie Herzschlagüberwachung für den Raspberry Pi ist an sich fertig, jetzt müssen wir den RasPi nur noch dazu bringen, auch einen Herzschlag abzugeben. Das ist die Aufgabe für den letzten Teil dieser Artikelserie. Der Raspberry Pi bekommt ein wenig Software verpasst, damit er ein regelmäßiges Pulssignal über einen Anschluss abgibt, das die Herzschrittmacher-Schaltung dann auswerten kann. Diese Software sollte nicht einfach neben der eigentlichen Nutzsoftware parallel herlaufen, wie wir es im ersten Beispiel machen werden. Sie sollte in die Anwendung integriert werden, so dass auch ein Fehler in der Anwendung zu einem Aussetzen des Herzschlags führt und damit zu einem zwangsweisen Neustart des Raspberry Pi.

Herzschlag – ganz einfach

Bevor es um die Feinheiten geht, schreiben wir einmal ein einfaches Python-Programm, das einen Herzschlag abgibt, um die  Schaltung überhaupt testen zu können:Herzschrittmacher-Schaltung auf einem Raspberry Pi 3

import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(4, GPIO.OUT)

#from datetime import datetime
#start=datetime.now()

while True:
  GPIO.output(4, not GPIO.input(4))
#  print (datetime.now()-start)
  time.sleep(1)

Mit sieben Programmzeilen ist das erledigt, wer eine Ausgabe der Zeit auf der Konsole möchte, der nimmt die drei auskommentierten Zeilen noch mit dazu. Für die reine Funktion werden sie nicht gebraucht. Wer schon mal den Raspberry Pi in Python programmiert hat, für den ist das banal, aber gehen wir das Programm kurz durch:

Die import Statements am Anfang importieren zwei Module. Einmal time, das brauchen wir, weil es die sleep() Funktion enthält und RPi.GPIO, um auf die IO-Pins zugreifen zu können. Warum bekommt RPi.GPIO beim Import den Namen GPIO? Das könnte man sich auch sparen, aber es hat sich so eingebürgert und jeder macht es so.

Dann geht es schon an die Schnittstelle. Mit setmode wird festgelegt, über welche Bezeichnung die Pins angesprochen werden. Über die Pinnummer auf dem Board (BOARD) oder die Broadcom-Kanalnummer (BCM). Im Prinzip ist das egal, man muss nur wissen, was man tut. Mit GPIO.setup() wird dann der Anschluss 4 als Ausgang definiert. Kanalnummer 4 in BCM-Notation entspricht dem Pin Nummer 7 auf der Steckleiste des Boards. Eine immer währende Verwechslungsquelle. Wir verwenden also BCM4, oft auch als GPIO4 bezeichnet, das ist gleichzusetzen mit Stift 7 auf der 40-poligen GPIO-Leiste.

while True: ist nichts anderes als eine Endlosschleife, also „mach immer!“. Und in dieser Schleife passieren zwei Dinge. Zuerst wird das Signal am Ausgang GPIO4 invertiert und dann legt sich das Programm für eine Sekunde schlafen. Die erste Zeile schauen wir uns nochmal genauer an:

GPIO.output(4, not GPIO.input(4))

Da wird also ganz rechts der GPIO4 Anschluss mit input() gelesen, was erst einmal ein bisschen paradox anmutet. In einen Ausgang sollte man schreiben und nicht von ihm lesen. Allerdings funktioniert das trotzdem, weil der Port vom letzten Schreibvorgang her noch einen Status hat, den man auch auslesen kann. Indem wir das tun, sparen wir uns, den Status in einer Variablen mitführen zu müssen. Nennen wir das ruhig „quick and dirty“. Das not invertiert nun den gelesenen Status: aus HIGH wird LOW und aus LOW wird HIGH. Auch das geht in Python, denn HIGH entspricht True und LOW entspricht False. Das Ergebnis der Invertierung wird dann mit output() direkt auf GPIO4 zurückgeschrieben.

Zum Ausprobieren schreiben wir die paar Zeilen in eine Datei und nennen sie beispielsweise heartbeat.py. Gestartet wird sie dann mit:

python3 heartbeat.py

Herzschrittmacher testen

Nachdem wir nun alle Komponenten (aus den vorhergegangenen Blog-Artikeln) und ein Software-Herzschlagsignal zur Verfügung haben, können wir erstmals die gesamte Schaltung testen.

Herzschrittmacher am Raspberry PiWenn sich die Herzschlag-Harwareschaltung an den richtigen Pins befindet, können wir die Stromversorgung an die Schaltung anlegen. Wohlgemerkt nur an die Herzschrittmacher-Schaltung und nicht zusätzlich an den Raspberry Pi. Wenn der RasPi nun hochfährt müssen wir schnell sein, um das heartbeat.py Programm zu starten, bevor die Hardware einen Timeout registriert. Mit den Standardzeiten im Mikrocontroller-Sketch sind das 30 Sekunden zum Booten und weitere 60 Sekunden zur Timeouterkennung. In Summe haben wir also 1½ Minuten Zeit. Sobald heartbeat.py läuft, wird die LED der Herzschlag-Schaltung im Sekundentakt blinken und der Raspberry Pi sollte beständig mit Strom versorgt werden.

Um eine Fehlfunktion zu simulieren, können wir nun einfach heartbeat.py beenden – also mit Strg-C abbrechen. Die LED wird aufhören zu blinken und nach Ablauf der voreingestellten Timeoutzeit von 60 Sekunden wird die Schaltung dem Raspberry Pi für 5 Sekunden den Strom abdrehen um einen Restart zu erzwingen. Damit bei jedem Boot das Herzschlagprogramm automatisch wieder anläuft, könnte man es beispielsweise in die /etc/rc.local Datei aufnehmen, in dem man dort vor dem exit 0 folgende Zeile einträgt:

cd /home/pi
su pi -c 'python3 -u heartbeat.py &'

Herzschlag in eigene Anwendungen integrieren

Heartbeat-Schaltung für 5vPer Python ein Herzschlagsignal auf einen Anschlusspin zu bekommen ist also recht simpel und mit wenigen Programmzeilen erreicht. Nun ist es aber nicht unbedingt sinnvoll, produktiv mit dem Testprogramm heartbeat.py zu arbeiten. Da es als eigenständiges Programm läuft, würde es nicht erkennen, wenn die Haupt-Software auf dem RasPi nicht mehr funktioniert. Heartbeat.py würde nur dann aufhören zu arbeiten, wenn es entweder selber abstürzt – was unwahrscheinlich ist – oder wenn sich der ganze Raspberry Pi aufhängt. Um die produktive Software zu überwachen, sollte die Herzschlag-Funktion so in die eigene Software integriert werden, dass nur dann ein Herzschlag erzeugt wird, wenn die Haupt-Software ordnungsgemäß funktioniert.

Wer zum Beispiel mein Kameraprojekt verfolgt oder nachgebaut hat, der könnte den periodischen Herzschlag in das Analyseprogram (analyzeX.py)  einbauen. Am besten in imageLoader.getImg(), denn dort wird jede Sekunde ein Bild geladen und wenn das erfolgreich ist, könnte ein einzelner Pulsschlag abgegeben werden. Sollte sich dann tatsächlich die Kamerafunktion aufhängen und kein neues Bild mehr geliefert werden, dann würde nach 60 Sekunden der Neustart des ganzen Systems erfolgen.

Ebenso lassen sich andere Python-Programme mit einem Herzschlag ausstatten, indem man ihn dort einbaut, wo die zentrale Funktion des Programms liegt. Und zwar so, dass immer dann ein Pulssignal abgegeben wird, wenn ein zentraler Schritt erfolgreich absolviert wurde und gleichzeitig eine Sekunde seit dem letzten Herzschlag vergangen ist.

Denkbar wäre es auch, dass ein eigenständiges Herzschlag-Programm parallel zu einer Hauptanwendung läuft und versucht deren Funktion zu überwachen. Zum Beispiel, ob bestimmte Prozesse aktiv sind, oder ob regelmäßig Dateien ins Filesystem geschrieben werden.

Herzschlag als Python-Klasse zur Integration in eigene Programme

Zur Vereinfachung stelle ich hier eine Herzschlagfunktion vor, die als Python-Klasse gestaltet ist. Diese Klasse kann in eigene Anwendungen übernommen werden und kümmert sich bei regelmäßigem Aufruf einer Methode von alleine darum, dass ein Herzschlag nur einmal pro Sekunde erfolgt.

import datetime
import RPi.GPIO as GPIO

heartBeatDelay = 1                                      # delay to change pin output
heartBeatPin = 4                                        # GPIO pin number
GPIO.setmode(GPIO.BCM)                                  # GPIO pin numbering mode

class heartBeat:                                        # The heartBeat class
  def __init__(self, pin, delay):                       # initial constructor
    self.pin = pin                                      # store pin number
    GPIO.setup(self.pin, GPIO.OUT)                      # set GPIO pin as output
    self.state = True                                   # store state (high/low as True/False)
    GPIO.output(self.pin, self.state)                   # write state to GPIO pin
    self.delay = datetime.timedelta(seconds = delay)    # transform given delay to timedelta object
    self.lastBeat = datetime.datetime.now()             # store time of heartbeat

  def pulse(self):                                      # method to call continously for heartbeat
    now = datetime.datetime.now()                       # get current time
    if now > self.lastBeat + self.delay:                # check for timed out delay time
      self.state = not self.state                       # invert state (True -> False and vice versa)
      GPIO.output(self.pin, self.state)                 # write new state to GPIO pin
      self.lastBeat = now                               # store time of this heartbeat
      print(now, end='\r')

hb = heartBeat(heartBeatPin, heartBeatDelay)            # create heartbeat object

try:
  while True:                                           # main program loop
    hb.pulse()                                          # repetitive call of heartbeat method
                                                        # do anything else here

except KeyboardInterrupt:
  print("\nProgram aborted by control-c")

finally:
  GPIO.cleanup()                                        # for beauty reasons

Herzschrittmacher-Schaltung auf einem Raspberry Pi 3Importieren müssen wir datetime für die Zeitsteuerung und RPi.GPIO für den Zugriff auf den Anschluss-Pin. Den können wir dann parametrisieren (hier GPIO4), genauso wie die Herzschlag-Pulsdauer (hier 1 Sekunde). Ein Objekt der Klasse heartBeat bekommt bei der Initialisierung die Pinnummer und das Herzschlagdelay übergeben, merkt sie sich und gibt auch gleich den ersten Pulsschlag auf die Leitung. In der Anwendung muss nichts weiter passieren, als einmalig ein Objekt der Klasse heartBeat zu erzeugen (hier hb) und dann periodisch dessen Methode pulse() aufzurufen. Im Beispiel wird in der endlosen Hauptschleife ständig hb.pulse() aufgerufen und dann alle anderen Funktionen, die das Programm sonst noch benötigen würde. Pulse() kümmert sich selber darum, dass nach Ablauf einer Sekunde ein Pulsschlag ausgegeben wird. Ein time.sleep(1)  wie im Testbeispiel oben ist hier nicht nötig, die pulse() Methode kann beliebig häufig aufgerufen werden. So lange keine weitere Sekunde vergangen ist, tut sie einfach überhaupt nichts. In einer realen Anwendung wird man den Aufruf von hb.pulse()von der erfolgreichen Ausführung einer zentralen Programmfunktion abhängig machen um diese Programmfunktion mit zu überwachen.

Die Herzschlagfrequenz ist keineswegs auf einen Wechsel pro Sekunde (30Hz) festgelegt. Durch den Parameter heartBeatDelay kann das Timing an die eigenen Bedürfnisse angepasst werden, wenn die einen schnelleren oder langsameren Herzschlag erfordern. Zu beachten ist allerdings, dass bei zu schnellem Takt der Mikrocontroller nicht überfordert wird und dass man umgekehrt bei sehr langsamem Takt nicht versehentlich in den Timeout läuft, wenn der Raspberry Pi mal etwas beschäftigt ist. In letzterem Fall kann natürlich im Mikrocontroller-Sketch die Timeout-Wartezeit verlängert werden.

 


Weitere Artikel in dieser Kategorie:

Schreiben Sie einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.