Mit replace Wörter ändern und den erst der Logdatei bei der ausgabe ignorieren

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
butzemania
User
Beiträge: 7
Registriert: Mittwoch 3. März 2021, 08:45

Mittwoch 3. März 2021, 09:38

Hallo,

leider habe ich nach langen suchen nichts gefunden und bräuchte ein wenig Hilfe, da ich mich nur mit Hardware auseinander setze ist das mit der script Programmierung nicht so mein ding. Ein kurzer Einblick.
ich möchte gerne verschiedene werte aus einer log Datei auf ein kleines OLED display ausgeben.
Dafür benutze ich die Letzte Zeile vom log. ich habe mich dazu entschlossen nicht die ganze text file einzulesen da diese ziemlich groß ist.
Mein test code sieht folgendermassen aus.

Code: Alles auswählen

def project ():
    rx_tx = os.popen("tail -n1 /var/log/svxlink | cut  -c22-").read()
    log = rx_tx
    senden = log.replace("Tx1: Turning the transmitter OFF","Offline").replace("Tx1: Turning the transmitter ON","Online")
    return senden
 
das gute an diesen code ist das es genauso funktioniert wie ich es will, das schlechte ist, da sich ja die letzte Zeile immer ändert und auch andere log Daten ausgibt werden diese natürlich auch angezeigt, diese möchte ich dann aber in einer anderen Definition zuweisen.
Meine Frage ist wie kann ich das bewerkstelligen das nur diese 2 Wort Gruppen abgearbeitet werden und der erst ignoriert wird und somit nicht in der Ausgabe landet. Und ich möchte später das Online und Offline mit einer Grafik versehen.
Benutzeravatar
sparrow
User
Beiträge: 2640
Registriert: Freitag 17. April 2009, 10:28

Mittwoch 3. März 2021, 13:29

Warum verwendest du denn ein externes Programm? Du schreibst doch gerade Code in einer vollständigen Programmiersprache.
Woran erkennst du denn die letzte Zeile, die du brauchst? Deine Problembeschreibung zeigt ja, dass es nicht reicht, ab Zeichen 22 zu schaun. Zeig doch die entsprechende, vollständige Zeile im Log.

Je nachdem, wie effizient es sein soll, könnte man in Python durch die Zeilen iterieren und nur die letzte Zeile "aufheben".
Es gibt aber auch Beispiele im Netz, die "roh" auf der Datei arbeite und sich von hinten nach vorne durch die Datei arbeiten und den ersten Zeilenumbruch suchen.
Ich würde wahrscheinlich ersteres tun, es sei denn, es gibt wirklich ein Performance-Problem.
Und auf dem Ergebnis kann man dann ja einfach mit Zeichenkettenfunktionen von Python arbeiten.
Benutzeravatar
__blackjack__
User
Beiträge: 8573
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mittwoch 3. März 2021, 14:11

@butzemania: Mir ist nicht wirklich klar was Du da eigentlich erreichen willst und wir wissen ja auch nicht wie die Zeilen in `/var/log/svxlink` insgesamt aussehen, nur den Teil den Du ersetzen möchtest.

Anmerkungen zum vorhandenen Quelltext: `project()` ist kein guter Name für eine Funktion. Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt die sie durchführen, damit der Leser weiss was die machen und sie von eher passiven Werten unterscheiden kann.

`os.popen()` würde man nicht verwenden. Zum starten von externen Programmen ist das `subprocess`-Modul da. Das würde dann so aussehen (ungetestet):

Code: Alles auswählen

    rx_tx = subprocess.run(
        "tail -n1 /var/log/svxlink | cut  -c22-",
        stdout=subprocess.PIPE,
        check=True,
        shell=True,
    ).stdout
Aber letztlich würde man dafür auch gar keine externen Programme starten wollen, sondern das in Python selbst lösen.

Die Umbenennung von `rx_tx` zu `log` ist überflüssige. Warum nicht bei `rx_tx` bleiben oder gleich `log` verwenden? Wobei `rx_tx` ein schlechter, weil sehr kryptischer Name ist, und `log` nicht so ganz zutrifft auf *eine* Zeile.

`senden` wäre ein guter Name für eine Funktion oder Methode aber nicht für eine Zeichenkette. Die sendet nichts. Man braucht aber auch nicht jedes Zwischenergebnis an einen Namen zu binden, insbesondere wenn da als nächstes sowieso nur ein ``return`` mit diesem Namen kommt.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import os

BUFFER_SIZE = 4096


def get_last_log_line():
    with open("/var/log/svxlink", "rb") as file:
        file.seek(-BUFFER_SIZE, os.SEEK_END)
        last_line = (
            file.read().rstrip(b"\n").rsplit(b"\n")[-1].decode("ascii")[22:]
        )

    return last_line.replace(
        "Tx1: Turning the transmitter OFF", "Offline"
    ).replace("Tx1: Turning the transmitter ON", "Online")
``tail`` macht noch ein bisschen mehr als nur *einmal* vom Dateiende zu `seek()`\en, weil man natürlich im Allgemeinen nicht davon ausgehen kann, das die letzte Zeile kleiner als 4096 Bytes ist.
“Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”
butzemania
User
Beiträge: 7
Registriert: Mittwoch 3. März 2021, 08:45

Mittwoch 3. März 2021, 14:22

Danke für die Antwort :)

Ich habe die externe Bash eingebunden da ich das übersichtlicher finde und auch im Terminal Fenster laufen habe. Leider ist meine programmier Kenntnis gleich null ,da ich mehr oder weniger mehr mit Hardware arbeite. Wie Schaltung löten etc.
Ich habe das OLED Display als gute Ergänzung empfunden.

die log Datei sieht wie folgt aus.

Code: Alles auswählen

03.03.2021 14:19:34: Tx1: Turning the transmitter OFF
03.03.2021 14:19:37: ReflectorLogic: Talker start on TG #1: REPEATER-NRW
03.03.2021 14:19:38: Tx1: Turning the transmitter ON
03.03.2021 14:19:38: ReflectorLogic: Talker stop on TG #1: REPEATER-NRW
03.03.2021 14:19:38: ### Talker stop on TG #1: REPEATER-NRW
03.03.2021 14:19:39: Tx1: Turning the transmitter OFF
03.03.2021 14:19:40: ReflectorLogic: Talker start on TG #1: REPEATER-NRW
03.03.2021 14:19:40: Tx1: Turning the transmitter ON
03.03.2021 14:19:41: ReflectorLogic: Talker stop on TG #1: REPEATER-NRW
03.03.2021 14:19:41: ### Talker stop on TG #1: REPEATER-NRW
03.03.2021 14:19:42: Tx1: Turning the transmitter OFF
03.03.2021 14:19:43: ReflectorLogic: Talker start on TG #1: REPEATER-NRW
03.03.2021 14:19:43: Tx1: Turning the transmitter ON
03.03.2021 14:19:44: ReflectorLogic: Talker stop on TG #1: REPEATER-NRW
03.03.2021 14:19:44: ### Talker stop on TG #1: REPEATER-NRW
03.03.2021 14:19:45: Tx1: Turning the transmitter OFF
wie man sehen kann ändert sich die Logfile in Sekunden Takt . die Datei Datei hat bereits eine Grösse von 3 MB
Benutzeravatar
sparrow
User
Beiträge: 2640
Registriert: Freitag 17. April 2009, 10:28

Mittwoch 3. März 2021, 15:42

Ungetestet (basierend auf dem Code von __blackjack__), ebenfalls ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import os

BUFFER_SIZE = 4096
LOGFILE = "/var/log/svxlink"

LOG_TO_TEXT = {
    "Tx1: Turning the transmitter ON": "Online",
    "Tx1: Turning the transmitter OFF": "Offline",
}

def get_last_log_line(path):
    with open(path, "rb") as file:
        file.seek(-BUFFER_SIZE, os.SEEK_END)
        last_line = (
            file.read().rstrip(b"\n").rsplit(b"\n")[-1].decode("ascii")[22:]
        )
        return last_line

def get_parsed_logline(path):
    last_line = get_last_log_line(path)
    date, time, text = last_line.split(maxsplit=2)
    return LOG_TO_TEXT.get(text, None)

if __name__ == "__main__":
    senden = get_parsed_logline(LOGFILE)
Du ersetzt ja die komplette Zeichenkette. Dann brauchst du kein replace sondern erstellst einfach eine neue.
Welche Zeichenkette welches Ergebnis ergibt, steht im Wörterbuch LOG_TO_TEXT
butzemania
User
Beiträge: 7
Registriert: Mittwoch 3. März 2021, 08:45

Mittwoch 3. März 2021, 15:54

]Zu meiner Logik, ich möchte das nur Tx1 im geänderten text auf dem Display ausgegeben wird. alle anderen Zeilen sollen ignoriert werden und nicht auf dem display erscheinen, diese möchte ich in einer extra def legen. Jetzt ist es so das er den geänderten text mit online und offline sauber ändert, aber auch die anderen Zeilen anzeigt. ich hänge mal den code an so wie er jetzt funktioniert. ich habe auch mal die vorschläge ausprobiert die oben beschrieben wurden, leider haben diese nicht funktioniert.

Code: Alles auswählen


#!/usr/bin/env python
import os
import sys
import time
import subprocess
from pathlib import Path
from datetime import datetime
from demo_opts import get_device
from luma.core.render import canvas
from PIL import ImageFont




def Tx1 ():
    rx_tx = os.popen("tail -n1 /var/log/svxlink | cut  -c22-").read()
    tx = rx_tx.replace("Tx1: Turning the transmitter OFF","Offline").replace("Tx1: Turning the transmitter ON","Online")
    return tx

def stats(device):
    font_path = str(Path(__file__).resolve().parent.joinpath('fonts', 'arial.ttf'))
    font1 = ImageFont.truetype(font_path, 10)
    font2 = ImageFont.truetype(font_path, 12)
    font3 = ImageFont.truetype(font_path, 13)
    font4 = ImageFont.truetype(font_path, 14)
    font5 = ImageFont.truetype(font_path, 15)
    font6 = ImageFont.truetype(font_path, 16)
    
    with canvas(device) as draw:
        draw.text((0, 20), Tx1() , font=font2, fill="white")
        
def main():
    while True:
        stats(device)
        time.sleep(0.1)
        


if __name__ == "__main__":
    try:
        device = get_device()
        main()
    except KeyboardInterrupt:
        pass
sorry da warst du schneller als ich :)
Benutzeravatar
__blackjack__
User
Beiträge: 8573
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mittwoch 3. März 2021, 17:03

@butzemania: Ich denke das ist alles ein bisschen zu kompliziert und man sollte eher der Logdatei folgen statt immer wieder die letzte Zeile auszulesen. Man muss für den Startzustand ja auch tatsächlich alle Zeilen auswerten (oder es wird *richtig* kompliziert) weil gar nicht anders feststellbar ist wo das letzte mal etwas zu dem gewünschten "Online"/"Offline"-Status ausgegeben wurde. Es sei denn es wäre okay am Anfang mit einem unbekannten Status zu starten.

Du sagst, dass Du kein Programmierer bist: Du wirst hier nicht darum herum kommen programmieren zu lernen. Denn Du willst ja etwas programmieren. Und das Problem hier ist auch gar nicht so einfach sinnvoll und effizient lösbar wie man auf den ersten Blick vielleicht denken mag. Man muss sich mindestens mal ein kleines ``tail -f`` selbst basteln.
“Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”
butzemania
User
Beiträge: 7
Registriert: Mittwoch 3. März 2021, 08:45

Mittwoch 3. März 2021, 17:19

ja ich werde mich mal rein knien, das gute andere log file ist es sind alles festgelegte variablen die mit ein start und stop versehen sind. deshalb interessiert mich immer nur die letzte Zeile. der Ansatz mit den Wörterbuch finde ich sehr gut so könnte ich jede Zeile zu ordnen und was ich nicht brauche mit ein Leerzeichen versehen.
Benutzeravatar
__blackjack__
User
Beiträge: 8573
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mittwoch 3. März 2021, 17:35

Zum gezeigten Quelltext: `subprocess`, `sys`, und `datetime` werden importiert aber nirgends verwendet.

`project()` wurde ich `Tx1()` umbenannt: Wie gesagt schlechte Namen. `Tx1` noch schlechter weil kryptische Abkürzung. Zu `tx_rx` hatte ich ja schon mal gesagt, dass der Name nicht gut ist. In Deiner Variante der Funktion braucht man im Grunde *gar keinen* Namen definieren.

`main()` greift auf die globale Variablen `device` zu. Globale Variablen verwendet man nicht. In dem ``if __name__ …``-Zweig sollte wirklich nur der Funktionsaufruf stehen.

Der Pfad zu den Schriftarten ist im Grunde eine Konstante, den braucht man nicht immer wieder neu definieren.

Man nummeriert keine Namen. So Nummernanhängsel sind nichtssagend. Entweder will man bessere Namen oder gar keine Einzelnamen für einzelWerte sondern eine Datenstruktur. Oft eine Liste. Im `font`-Fall werden bis auf einen der Namen die anderen aber auch gar nicht verwendet, also macht die Nummerierung noch weniger Sinn.

Entsprechend überarbeitet (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import os
import time
from pathlib import Path

from demo_opts import get_device
from luma.core.render import canvas
from PIL import ImageFont

FONT_PATH = Path(__file__).resolve().parent / "fonts" / "arial.ttf"


def get_last_log_line():
    return (
        os.popen("tail -n1 /var/log/svxlink | cut  -c22-")
        .read()
        .replace("Tx1: Turning the transmitter OFF", "Offline")
        .replace("Tx1: Turning the transmitter ON", "Online")
    )


def stats(device):
    with canvas(device) as draw:
        draw.text(
            (0, 20),
            get_last_log_line(),
            font=ImageFont.truetype(str(FONT_PATH), 12),
            fill="white",
        )


def main():
    try:
        device = get_device()
        while True:
            stats(device)
            time.sleep(0.1)
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()
Mit einer simplen ``tail``-Implementierung könnte das so aussehen (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import os
import time
from pathlib import Path

from demo_opts import get_device
from luma.core.render import canvas
from PIL import ImageFont

LOG_FILE_PATH = Path("/var/log/svxlink")
TIMESTAMP_LENGTH = 21
TRANSMITTER_STATE_PREFIX = "Tx1: Turning the transmitter "
TRANSMITTER_STATE_TO_DISPLAY_TEXT = {"ON": "Online", "OFF": "Offline"}

FONT_PATH = Path(__file__).resolve().parent / "fonts" / "arial.ttf"


def follow_lines(filename):
    with open(filename, encoding="ascii") as file:
        file.seek(0, os.SEEK_END)
        position = file.tell()
        while True:
            line = file.readline()
            if line and line.endswith("\n"):
                yield line
                position = file.tell()
            else:
                file.seek(position)
                time.sleep(0.5)


def update_display(device, transmitter_state):
    with canvas(device) as draw:
        draw.text(
            (0, 20),
            transmitter_state,
            font=ImageFont.truetype(str(FONT_PATH), 12),
            fill="white",
        )


def main():
    try:
        device = get_device()
        transmitter_state = "Unknown"
        update_display(device, transmitter_state)
        for line in follow_lines(LOG_FILE_PATH):
            line = line[TIMESTAMP_LENGTH:].rstrip()
            if line.startswith(TRANSMITTER_STATE_PREFIX):
                transmitter_state = TRANSMITTER_STATE_TO_DISPLAY_TEXT[
                    line[len(TRANSMITTER_STATE_PREFIX) :]
                ]
                update_display(device, transmitter_state)

    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()
“Dawn, n.: The time when men of reason go to bed.” — Ambrose Bierce, “The Devil's Dictionary”
butzemania
User
Beiträge: 7
Registriert: Mittwoch 3. März 2021, 08:45

Donnerstag 4. März 2021, 14:28

@blackjack
Danke für deine super Hilfe, dein letzter code Schnipsel erfüllt genau das was ich wollte habe noch andere Auswertungen hinzugefügt wie Talker Start und Stop und das funktioniert auch.

einzig nach einer bestimmten Laufzeit bricht das script ab und meldet folgendes:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/pi/ssd1306/examples/luma.examples/examples/svxlink4.py", line 60, in <module>
    main()
  File "/home/pi/ssd1306/examples/luma.examples/examples/svxlink4.py", line 47, in main
    for line in follow_lines(LOG_FILE_PATH):
  File "/home/pi/ssd1306/examples/luma.examples/examples/svxlink4.py", line 23, in follow_lines
    line = file.readline()
  File "/usr/lib/python3.7/encodings/ascii.py", line 26, in decode
    return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 50: ordinal not in range(128)
habe den code mal wie folgt geändert und hoffe es hilft

Code: Alles auswählen

with open(filename, encoding="ascii") as file:
in:

Code: Alles auswählen

with open(filename, encoding="iso-8859-1") as file:

geändert, muss erstmal wieder eine weile laufen bis ein Fehler kommt.
butzemania
User
Beiträge: 7
Registriert: Mittwoch 3. März 2021, 08:45

Donnerstag 4. März 2021, 14:30

Achso ich denke das dies die log Zeile ist die den Fehler auslöst:

Code: Alles auswählen

04.03.2021 14:18:17: ReflectorLogic: UDP frame(s) lost. Expected seq=28809 but received 28810. Resetting next expected sequence number to 28811
04.03.2021 14:18:17: ReflectorLogic: Dropping out of sequence UDP frame with seq=28809
04.03.2021 14:18:17: Tx1: Turning the transmitter ON
butzemania
User
Beiträge: 7
Registriert: Mittwoch 3. März 2021, 08:45

Freitag 5. März 2021, 13:54

Habe jetzt mal zum test alles weiter laufen lassen, nach der Änderung auf ISO norm hat alles geklappt. Der Fehler war einfach nur, das auch umlaute vorkommen. :(

Aber eines würde mich noch brennend interessieren. Ich bekomme einfach meine Uhr mit Sekunden anzeige nicht mehr ans laufen. Sie wird zwar angezeigt ,aktualisiert sich nur wenn auch der Transmitter sich aktualisiert. Aber ich wollte das sie in schleife läuft und auch die Sekunden anzeigt.

hatte das bisher so gemacht:

Code: Alles auswählen

def akt_zeit ():
    zeit = datetime.now().strftime('%H:%M:%S')
    return zeit
    
und in main() eine while mit time.sleep(0.1) laufen lassen.
Antworten