Monitoring Die PIUSV+

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • (Zurück zum Inhaltsverzeichnis)
    Einleitung:
    Die PIUSV+ ist eine USV die von der Fa. Ritter Elektronik hergestellt wird.
    Hier geht es darum, die elektrischen Werte der PIUSV+ mittels Diagramme grafisch darzustellen.
    Es lassen sich 5 Werte aus der PIUSV+ auslesen: U_Batt, U_Rasp, I_Rasp, U_USB, und U_Ext.
    Die Diagramme werden alle 5 min automatisch aktualisiert.
    Voraussetzung ist, das SNMP, MRTG und die PIUSV+ bereits eingerichtet sind.
    Der Client ist hier mit einer PIUSV+ ausgerüstet, die mit einem Python-Script ausgelesen wird.
    Die Daten werden dann mittels SNMP über Bash-Scripte abgefragt.
    Ein anderer Raspberry (Server) ruft die Daten mittels MRTG ab und liefert sie über einen Webserver (Apache) aus.

    Das Auslesen der PIUSV+
    Zum Einsatz kommt eine modifizierte Python-Software aus dem Raspberry-Forum:<br />
    Raspberry-Forum: (Beitrag 4) und einer Modifikation (Beitrag 14)
    Hier bereits zusammengefügt:

    Code
    sudo vi /usr/local/piusv.py


    Die Modifikation ist der Abschnitt zwischen "# SNMP-Abfrage" und "# Kommandozeilenausgabe komplett"
    Sie bewirkt, das die PIUSV+ nur einmal über den I2C-Bus ausgelesen wird, aber alle benötigten Werte liefert.
    Nun noch ausführbar machen und das Script mit:

    Code
    pisuv.py snmp


    aufrufen. Danach sollten sich 5 Dateien in /tmp befinden:

    Code
    I_Rasp.snmp
    U_Batt.snmp
    U_ext.snmp
    U_Rasp.snmp
    U_USB.snmp


    Die Angaben in diesen Files sind in mV bzw. in mA.
    Der Aufruf "pisuv.py SNMP" gibt diese Werte auch im Terminal aus.

    Der Cronjob
    Dieses Script sollte alle 5 min. aufgerufen werden:

    Code
    sudo vi /etc/cron.d/piusv


    Mit diesem Inhalt:

    Code
    */5 *   * * *   root    python /usr/local/bin/piusv.py snmp 2>&1


    Die Bash-Scripte für SNMP
    Die Abfrage vom MRTG-Server greift auf die in /tmp abgespeicherten Dateien mittels dieser Bash-Scripte zu:
    Der Vorteil von dieser Vorgehensweise:
    a) Die Werte werden immer zu einem bestimmten Zeitpunkt ermittelt.
    b) Wann die Werte abgefragt werden ist ziemlich egal.
    Nachteil: Wenn die Abfrage der PIUSV+ schiefgeht, ändern sich die Werte nicht mehr.

    Code
    sudo vi /usr/local/bin/snmp-piusv_U_Batt.sh
    Bash
    #!/bin/bash
    tmp=`cat /tmp/U_Batt.snmp`
    if test $tmp -gt 5000
    then
      tmp=5000
    fi
    echo .1.3.6.1.2.1.25.1.20
    echo gauge
    echo $tmp
    exit 0
    Code
    sudo vi /usr/local/bin/snmp-piusv_U_Rasp.sh
    Bash
    #!/bin/bash
    tmp=`cat /tmp/U_Rasp.snmp`
    if test $tmp -gt 6000
    then
      tmp=6000
    fi
    echo .1.3.6.1.2.1.25.1.21
    echo gauge
    echo $tmp
    exit 0
    Code
    sudo vi /usr/local/bin/snmp-piusv_I_Rasp.sh
    Code
    !/bin/bash
    tmp=`cat /tmp/I_Rasp.snmp`
    if test $tmp -gt 1500
    then
      tmp=1500
    fi
    echo .1.3.6.1.2.1.25.1.22
    echo gauge
    echo $tmp
    exit 0
    Code
    sudo vi /usr/local/bin/snmp-piusv_U_USB.sh
    Bash
    #!/bin/bash
    tmp=`cat /tmp/U_USB.snmp`
    if test $tmp -gt 6000
    then
      tmp=6000
    fi
    echo .1.3.6.1.2.1.25.1.23
    echo gauge
    echo $tmp
    exit 0
    Code
    /usr/local/bin/snmp-piusv_U_ext.sh
    Code
    !/bin/bash
    tmp=`cat /tmp/U_ext.snmp`
    if test $tmp -gt 26000
    then
      tmp=26000
    fi
    echo .1.3.6.1.2.1.25.1.24
    echo gauge
    echo $tmp
    exit 0


    Es sind hier mehrere "If Then" Abfragen zu sehen, sie begrenzen die $tmp Variable mit einen plausiblen Maximalwert.
    Hier ist es schon vorgekommen, das der Raspberry sich 56A genehmigt hatte und damit das Diagramm von MRTG wegen der dynamischen Anpassung unleserlich gemacht hatte.
    Ich vermute mal, das das Python-Script die PIUSV+ abgefragt hatte, während der Prozessor der PIUSV+ neue Daten in die Register abgelegt hat.

    SNMP-Erweiterung
    In der:

    Code
    /etc/snmp/snmpd.conf


    wird der pass-Abschnitt um folgende Einträge ergänzt:

    Code
    pass .1.3.6.1.2.1.25.1.20 /bin/sh /usr/local/bin/snmp-piusv_U_Batt.sh
    pass .1.3.6.1.2.1.25.1.21 /bin/sh /usr/local/bin/snmp-piusv_U_Rasp.sh
    pass .1.3.6.1.2.1.25.1.22 /bin/sh /usr/local/bin/snmp-piusv_I_Rasp.sh
    pass .1.3.6.1.2.1.25.1.23 /bin/sh /usr/local/bin/snmp-piusv_U_USB.sh
    pass .1.3.6.1.2.1.25.1.24 /bin/sh /usr/local/bin/snmp-piusv_U_ext.sh


    Die OID-Liste befinden sich in einem anderen Beitrag, aber Achtung, es handelt sich zum Teil um selbst definierte OIDs, nicht um offizielle.

    Die Software des Servers (MRTG-Erweiterung)
    Folgende Ergänzung fehlt noch zur in der /etc/mrtg/raspiXX_mrtg.cfg (raspiXX durch eigenen Namen oder IP-Adresse ersetzen).

    Und nun noch die Include-Dateien

    Code
    sudo vi /etc/mrtg/raspi_PIUSV_-_U_rasp.inc
    Code
    sudo vi /etc/mrtg/raspi_PIUSV_-_I_rasp.inc
    Code
    sudo vi /etc/mrtg/raspi_PIUSV_-_U_USB.inc
    Code
    sudo vi /etc/mrtg/raspi_PIUSV_-_U_ext.inc
    Code
    sudo vi /etc/mrtg/raspi_PIUSV_-_U_batt.inc

    Letzte Anweisung:

    Code
    sudo indexmaker --output=/var/www/mrtg/raspiXX/index.html /etc/mrtg/raspiXX_mrtg.cfg


    Und dann ein bißchen (ca. 10min.-15min.) warten...

    Edit: Fiptehler und Links eingefügt

  • Nachtrag:

    Die Scripte für das Monitoring sind noch in Python 2 geschrieben, ich hatte jetzt per Zufall ein Python3-Script gefunden:

    https://github.com/daq-tools/rpi-piusv

    Getestet mit Bullseye64 auf einen 3B+. Sieht zwar etwas anders aus, aber das ist schonmal eine Grundlage für mehr.

    Es ist aber nicht kompatibel mit den Sripten im vorherigen Artikel, da steckt noch Arbeit drin.

    Das sieht zwar auch vielversprechend aus, funktioniert aber (z.Z.) nicht.

    https://github.com/dehapama/piupsmonitor-raspberrypi

    Kompilieren läßt es sich zwar, aber nur mit: gcc -O -o usr/sbin/piupsmonitor src/piupsmonitor.c -li2c

    Das habe ich schon mal hingekriegt, aber das Programm stürzt anschließend mit:

    Code
    sudo /usr/sbin/piupsmonitor Opening device... OK
    Speicherzugriffsfehler

    ab. Anschließend muss man das PID-File mit: ssudo rm /run/piupsmonitor.pid löschen.

    Da weiss ich noch nicht weiter.

    MfG

    Jürgen

    Edit: Fiptehler

    Edit 2: Sollte der Monitor funktionieren, könnte man die PIUSV+ auch noch länger betreiben, aber den Raspberry4B muss ich erstmal testen.

    Edit 3: Es sieht so aus als wenn der der Monitor funktioniert: Siehe [Tutorial][PIUSV+] Alternative zu piupsmon-0.9.deb von CW2

  • Da weiss ich noch nicht weiter.

    Ich bin doch weitergekommen, der Fehler war, wie bei meisten Fällen, zwischen Stuhl und Bildschirm.

    Man muss nur mal richtig hinsehen.

    Da fehlten noch 2 Vorrausetzungen:

    sudo mkdir /etc/piupsmonitor/

    sudo cp /etc/piupsmon/piupsmon.conf /etc/piupsmonitor/piupsmonitor.conf

    Beim Loglevel=NOTICE in der piupsmonitor.conf erscheinen dann auch ein paar Log-Meldungen:

    Damit kann man die PIUSV+ unter Buster (armhf und arm64 noch nicht gestestet) und Bullseye (nur mit RPi3B+ arm64 getestet) erstmal weiterverwenden.

    MfG

    Jürgen

  • Hier ist mal eine erste Vorabversion eines Python3-Scriptes zum Auslesen der PIUSV+:

    Ist noch gespickt mit Debug-Zeilen.

    Wurde aus der Python2-Version aus #1 erstellt, aber diese Version läuft erstmal ohne Fehler.

    Ja ich weiss, schön geht anders.

    MfG

    Jürgen

  • with open() wurde ja in dem anderen Thread schon genannt. Ab Python3.6 gibt es f-Strings. Ich finde das übersichtlicher als mit format() zu arbeiten. Z.B. der Eintrag ab Zeile 165 könnte dann so aussehen:

    Python
    with open('/tmp/U_Batt.snmp', 'w') as op:
        op.write(f'{piusv.par_2[0]:0.0f}\n')

    Ich gehe davon aus, dass es sich um float handelt, der ohne Komma gerundet werden soll.

  • Wie schonmal geschrieben, ist das eine schon etwas ältere Python2-Software, die ich erstmal nur an Python3 angepasst hatte.

    Angepackt hatte ich die weil ich auf ein paar interessante Sachen gestossen bin mit denen ich nicht mehr gerechnet hatte.

    So die Sourcen für einen alternativen Monitor. War so etwas wie ein kleines Weihnachtsgeschenk das das auch noch funktionierte.

    Mit euren Vorschlägen werde ich mich mal heute Abend beschäftigen. Aber in Python3 stecke ich noch nicht so drin.

    MfG

    Jürgen

  • Hm, wenn der Unterstrich im Terminal ein Problem ist, dann würde ich persönlich über eine andere Schriftart für's Terminal nachdenken. Die muss IMHO genau so zum programmieren geeignet sein wie die, die man im Editor oder in der IDE verwendet, denn man sieht ja öfter mal Quelltext im Terminal, zum Beispiel auch bei einer Versionskontrolle die Diffs oder ähnliches, und vielleicht auch in Vim oder wenn alle Stricke reissen Nano wenn man mal schnell was per SSH auf einem entfernten Rechner bearbeiten muss.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Ich habe mir jetzt mal den Quelltext angeschaut und ich würde das umgekehrt sehen: Nicht die ``return``\s die den Objektzustand verändern und das was sie gerade verändert haben zurückgeben sind das Problem, sondern was da alles zum Objektzustand gemacht wird und eigentlich nur lokal und/oder als Rückgabewert existieren sollte.

    `log` und `status` werden zudem auch noch ausserhalb der `__init__()` als Attribute eingeführt. Und bei `version()` überschreibt sich die Funktion selbst mit einem gleichnamigen Attribut, das heisst die Methode funktioniert genau ein mal.

    In der Hauptfunktion ist ein ``except`` ohne konkrete Ausnahme. Das behandelt *alle* Ausnahmen, auch solche mit denen man gar nicht rechnet, und dafür gibt es nur sehr wenige sinnvolle Behandlungen. In der Regel nur wenn da auch protokollieren und/oder die Ausnahme erneut auslösen mit drin steht, damit man eine Chance auf eine sinnvolle Fehlersuche hat. Hier wird aber ziemlich klar auf einen `IndexError` reagiert, also könnte man den auch hinschreiben.

    Wenn eine unbekannte Option angegeben wurde, würde ich die Ausgaben und auch einen Rückgabecode ≠ 0 an den Aufrufer zurückgeben wie beim Fall das keine Option angegeben wurde.

    Mag ein bisschen kleinlich klingen, aber ich lese bei `piusv` immer Papst Pius den fünften. Zwischen `pi` und `usv` würde ich einen Unterstrich setzen (oder den `pi`-Präfix ganz weglassen).

    U_Batt und Kollegen stehen mehrfach als Namen und als Zeichenketten im Quelltext. Und Informationen dazu in verschiedenen Formen über den ganzen Quelltext verteilt und auch wiederholt hart im Code. Das sollte man sinnvoll zusammenfassen.

    Die Verbindung zur USV wird nicht sauber geschlossen. Dafür macht man das `PiUSV`-Objekt am besten zum Kontextmanager, damit man es mit ``with`` verwenden kann.

    Die Ausnahmebehandlung beim Lesen ist kaputt. Wenn da eine Ausnahem ausgelöst wird, dann wird ein Text ausgegeben und danach einfach weitergemacht als wäre gar kein Fehler aufgetreten. Teilweise mit der Folge, dass dann falsche Werte ausgegeben werden. Die Ausgabe der Fehlermeldung gehört nicht in die Methoden von der Programmlogik. Zudem ist das ja immer die selbe Ausgabe, also kann man das *einmal* in der Hauptfunktion behandeln, für alle Aufrufe. Oder auch ganz sein lassen, dann bekommt man im Fehlerfall auch gleich den Traceback.

    Die `PiUSV`-Klasse: Die hat eigentlich nur `piusv` als Attribut. Alles andere wird entweder nicht benutzt, ist eine Konstante, oder gehört dort nicht als Attribut hin. Und `piusv` sollte anders heissen. Die Attribute sind ja die Bestandteile so eines Objekts, und der Satz „Ein PiUSV-Objekt besteht aus einem `piusv`“ beziehungsweise wenn es nur ein Attribut ist „Ein PiUSV-Objekt kapselt/wrapped ein `pisusv`“ klingt schon irgendwie falsch.

    Zu den anderen Attributen: `address` ist eine Konstante. `par_name` entweder auch, oder sollte einfach als lokaler Name in der einzigen Methode definiert werden, in der dieser Wert verwendet wird.

    Die Attribute `data_0` und `par_3` werden nirgends verwendet.

    `data_1` und `data_2` sind Konstanten, die aber dringend einen besseren Namen bekommen sollten.

    Und `par` und `par_2` sollten gar keine Attribute sein.

    `get_parameter()` sollte eher `get_parameters()` heissen.

    `word2float()` und `word2int()` sind identisch bis auf eine Division durch 1000. `word2float()` würde ich da als überflüssig ansehen. Die Milli*-Angaben sind präziser, wegen der Ungenauigkeit die man sich bei Gleitkommazahlen einfängt, und die Ausgabe mit Komma/Dezimalpunkt ist IMHO eher eine Frage der Kommunikation mit dem Benutzer.

    Die `line()`-Methode gehört IMHO nicht auf die Klasse.

    Da ist auch ein „anti pattern“ drin was öfter vorkommt: Zeichenketten in Schleifen mit ``+`` oder ``+=`` erweitern ist ineffizient, weil Zeichenketten unveränderlich sind und deshalb immer mehr und mehr Daten bei jedem Erweitern im Speicher sinnlos herumkopiert werden müssen. Idiomatisch wäre es die Einzelteile in einer Liste zu sammeln oder einen Generatorausdruck zu schreiben, und das dann von der `str.join()`-Methode zusammensetzen zu lassen.

    Ungetesteter Zwischenstand:

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Wow, funktioniert fast auf Anhieb.

    Zwischen Zeile 23 und 24 fehlt noch ("U_Rasp", "V"),

    Bei der Option version kommt das heraus: v0., aber nur beim ersten Aufruf,

    beim 2. Aufruf dann die richtige Ausgabe: v0.1.6 klu

    Und es wird immer folgendes mit ausgegeben z.B.:

    Bei jeder Option. Anmerkung: Diese "Unschönheit" wurde in dem nachfolgenden Script bereits korrigiert, siehe auch #12

    Ich hab noch die Reihenfolge der log- und der all-Ausgabe geändert, damit die Ausgabe kompatibel zu dem alten Script ist.

    Warum? Ich weiß nicht wer das Script alles benutzt und diese Ausgaben eventuell auswertet.

    Ich versuch noch zu verstehen wie das ganze funktioniert. Meine Python3 Bücher sind anscheinend hoffnungslos veraltet.

    MfG und vielen Dank für die Mühe

    Jürgen

  • Ups, ja der Teil ab Zeile 186 bis zum Ende der Funktion müsste in ein ``else`` damit der nicht nach anderen Optionen auch noch bedingungslos ausgeführt wird. ?

    Bei `get_version()`: Kann es vielleicht sein, dass die Gegenseite dort, wie beim auslesen der Werte, vielleicht nach dem Kommandobyte ein winziges kleines Päuschen braucht?

    Ich glaube so rein sprachlich benutze ich ausser f-Zeichenkettenliteralen und ``*`` um iterierbares direkt in ein Listenliteral zu entpacken aber nichts was es nicht auch schon in Python 2 gegeben hätte. Und nur der ``*`` ist nicht direkt ersetzbar — statt f-Zeichenkettenliteralen hätte man in Python 2 die `format()`-Methode auf Zeichenketten verwendet. An Bibliotheken ist nur `pathlib` neu. Dafür gibt es für Python 2 einen Backport.

    Edit: Die Teile mit dem ``*`` in Listenliteralen hätte man in Python 2 mit `chain.from_iterables()` aus dem `itertools`-Modul in der Standardbibliothek umsetzen können, ohne da gross was an der Logik zu ändern.

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Ups, ja der Teil ab Zeile 186 bis zum Ende der Funktion müsste in ein ``else`` damit der nicht nach anderen Optionen auch noch bedingungslos ausgeführt wird.

    Das war es, jetzt funktioniert das auch. Für eine "Blind"-Programmierung war das eine hervorragende Arbeit.

    Ich hab mal die Sourcen in #11 angepasst, um nicht nochmal die Sourcen zu posten.

    In Zeile 67 ist ein time.sleep (0.0001) hinzugekommen, aber ob der hilft weiß ich aber noch nicht.

    Bis jetzt hab das unter Bullseye64 gestestet, mal sehen ob das auch unter Buster32/64 funktioniert.

    Gibt es eigentlich eine Untergrenze der Python3-Versionen mit der das Script noch funktioniert?

    MfG und nochmal Danke für die Hilfe

    Jürgen

    Edit_01: Buster32 funktioniert (Python 3.7)

  • die 'f'-Strings sind bestimmt der Punkt, der die Version nach unten begrenzt. Sie wurden in der Python-Version 3.6 eingeführt:

    Dann muss ich ja den RPi, der hier noch unter "Stretch" läuft nicht mehr quälen.

    MfG

    Jürgen

    Edit: Wurstfingereffekt korrigiert

  • Beitrag von hyle (22. Dezember 2021 um 22:06)

    Dieser Beitrag wurde gelöscht, Informationen über den Löschvorgang sind nicht verfügbar.
  • Untergrenze ist bei mir normalerweise die älteste offiziell unterstützte Version. Also diesen Monat noch 3.6, ab nächsten Monat 3.7. Ich werde also noch eine gaaaanze Weile `pairwise()` aus dem externen `more_itertools` statt aus `itertools` aus der Standardbibliothek importieren. ?

    “Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”

  • Schade, dass du die ganzen Kommentare gelöscht hast.

    Code: Aus #1
    class TEST_1: # Platzhalter
        # Statusbyte auslesen
        def get_status(self):
            ...

    Wäre so besser:

    Code
    class TEST_2: # Platzhalter
        def get_status(self):
            """
            Statusbyte auslesen
            """

    Die Klassen TEST_1 und TEST_2 sind nur dazu da, damit der Syntax gültig ist und es sich weiterhin beim Test um eine Klasse handelt.


    In der REPL:

    Code
    help(TEST_1.get_status)
    Zitat

    Help on function get_status in module __main__:

    get_status(self)

    # Statusbyte auslesen

    (END)

    Code
    help(TEST_2.get_status)
    Zitat

    Help on function get_status in module __main__:

    get_status(self)

    Statusbyte auslesen

    (END)

    Also den ganzen Kram, den Menschen lesen sollen, in die Docstrings packen.

    Was macht die Funktion/Methode? Welche Argumente benötigt sie. Welche Objekte gibt sie aus.

    Was macht das Modul (Modul == die Python-Quelldatei). Was macht das Package (viele Module zusammengefasst).

    Wenn erst mal nur die Methoden/Funktionen einen Docstring haben, ist das erst mal für den Anfang ausreichend.

    Mach die Dokumentation für dich, nicht für andere.

    Andere könmnten davon auch profitieren, aber wenn du selbst in einem Jahr den Code siehst und dort keine Docstrings drin sind, wirst du erst mal suchen. Es ist auch legitim Verweise auf Links zu setzen, von denen man z.B. einen Algorithmus kopiert hat oder etwas erklärt wird.

    Wenn man konsequent jedem Modul, Klasse, Methode und Funktionen einen Docstring zuweist, hat man eine gute Dokumentation. Tools wie Sphinx (auch in Python geschrieben) können dann aus dem Quellcode eine Dokumentation z.B. als HTML oder PDF erstellen.

  • Hallo Jürgen,

    auf dem Forum Raspberry Pi habe ich dein Tutorial "Alternative zu piupsmon-0.9.deb von CW2" gelesen.

    Die PIUSV+ von CW2 habe ich mit der Bibliothek piupsmon-0.9.deb schon für früherer Projekte verwendet.

    Da diese Bibliothek unter Bullseye x64 nicht mehr funktioniert, habe ich deine Scripte verwendet.

    Soweit funktioniert auch alles, jedoch wird kein Shutdown ausgeführt, wenn der Pi vom Netz getrennt wird

    Hast due ein Tipp für mich, wo da der Wurm liegen könnte?

    Mit besten Grüßen

    Thomas

  • Es hat hat am 24.11.2023 ein Update der Sourcen gegeben:

    History for src - dehapama/piupsmonitor-raspberrypi
    Programm to monitor the PiUPS+ (PiUSV+) module for the Raspberry Pi - History for src - dehapama/piupsmonitor-raspberrypi
    github.com

    MfG

    Jürgen

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!