Prioritäten bei Threads

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.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dann muss man mit dem Thread kommunizieren, beispielsweise über eine `Queue`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ihr müsst euch mal über eure Prioritäten kar werden. Sprichwörtlich und im übertragenen Sinne. Ihr habt einen Riesen Code verhau, der seine Grundfunktion Daten zuverlässig zu erfassen nicht beherrscht, aber die Status LED ist plötzlich wichtig?!?!

Das kann man später immer noch drandekorieren. Und wenn man eine Blink-LED kauft & die an die Stromversorgung des PI hängt.

Aber eure verkorkste Datenaufnahme der Kanäle ohne Aussetzer - die muss laufen.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Es geht mir grundlegend drum - wie/wann soll man 2 Threads gleichzeitig laufen lassen?
Ist ja das selbe mit dem Einlesen und weiterverarbeiten.

Einlesen und "Impulslog" - wird so schnell wie möglich ausgeführt.
Verarbeiten und "Wertlog" ist mehr code - wird nur alle 3 Sekunden ausgeführt.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Python kann keine Threads gleichzeitig. Die werden immer seriell abgearbeitet, mit diversen Problemen die das verursachen kann. Worauf ja auch schon noisefloor hinwies. Darum kann man die I2C Kommunikation und den Rest nicht im selben Prozess machen - wenn es rund laufen soll. Sondern brauch 2 Prozesse, und die müssen kommunizieren.

Und DAS ist eure große Aufgabe. Ob man dann später nebenbei irgendeinen Blink-Thread dödeln lässt oder da gar ein eigenes Programm für schreibt (was eher Zuviel des guten ist, aber auch geht) - was soll’s. Würde ich gaaaaaaaaaaaanz hinten anstellen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Muntliger: man will keine Threads benutzen. Nur manchmal ist man dazu gezwungen. Und wann das der Fall ist, kommt auf den Anwendungsfall an. Grundlegend kann man keine Antwort darauf geben.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Hat jemand erfahrung mit dem logging Modul?
das erste File wird normal gefüllt - danach steht jeweils nur noch eine Line im Log File.

Code: Alles auswählen

def setup_logger(logger_name, log_file):
    l = logging.getLogger(logger_name)
    l.setLevel(logging.INFO)

    fileHandler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100, backupCount=10)
    formatter = logging.Formatter('%(asctime)s;%(message)s;test')
    fileHandler.setFormatter(formatter)
    l.addHandler(fileHandler)
Hab ich hier ein Fehler im Code?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Also bei mir stehen zwei Zeilen. Wenn ich einen sehr kurzen log-string nehme. Und das wird wohl an der absurd kurzen Anzahl von Bytes, die du pro Logfile zulaesst, liegen.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

100 bytes hab ich nur zum test genommen.
Aber es werden keine 100 bytes geschrieben.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Alleine deine Datums und Zeitangaben sind schon knapp 30 Zeichen. OHNE das du einen eigenen String loggst ist nach 3 Zeilen Schluss. Denn 100 Bytes ist eine OBERGRENZE. Denn es hat ja keiner was davon, wenn eine log-ausgabe mittem im Text abgeschnitten und im naechsten File fortgefuehrt wird, oder? Wenn du also auch nur irgendwas laenger als 20 Zeichen loggst, dann hast du schon > 50 Zeichen, und nur noch eine Nachricht pro Logfile.

Das ist schon der Grund. Das kannst du mir durchaus glauben, und mal ausprobieren, was passiert, wenn du mal mehr als 100 Bytes nimmst. Um dafuer ein Verstaendnis zu kriegen.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Ok, danke - hab’s getestet.

Hab die Software nun nochmal neu geschrieben.
Ein "hochpriorisierter" Prozess liest die i2c Eingänge und schreibt nur bei einem Puls den Timestamp in ein Queue - der "Normale" Prozess macht dann alles weitere wie loggen und Wert berechnen.
Die Ganzen Config Sachen werde ich nächste Woche auch noch dazu bauen.
Mal sehen ob diese Nacht durchgeloggt wird, ohne ein Impuls zu vergessen.

Der Code ist, dank eueren Tipps auf 170 Zeichen geschrumpft.
Aber es gibt trotzdem sicherlich noch einige Punkte zu verbessern.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zeig mal her, damit man das genauer beurteilen kann. Klingt aber schon besser!
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from OmegaExpansion import onionI2C
import time
import logging
import logging.handlers
import importlib
import multiprocessing
from multiprocessing import Process, Queue
import threading
import os
import datetime
import configparser

def setup_logger(logger_name, log_file):
    l = logging.getLogger(logger_name)
    l.setLevel(logging.INFO)

    fileHandler = logging.handlers.RotatingFileHandler(log_file, maxBytes=10000000, backupCount=10) # 10MB x 10
    formatter = logging.Formatter('%(message)s')
    fileHandler.setFormatter(formatter)
    l.addHandler(fileHandler)

def read_i2c(read_i2c_queue):
    i2c = onionI2C.OnionI2C()
    i2cdata = [0xFF]
    i2c.write(0x20, i2cdata)

    act_channel = 6
    channel_ppulse = [False,False,False,False,False,False]
    channel_ppulse_hm = [False,False,False,False,False,False]
    last_pulsetime = [0,0,0,0,0,0]
    act_pulsetime = [0,0,0,0,0,0]

    while True:
        os.nice(-20) # fast process
        act_channel += 1
        if act_channel > 5:
            act_channel = 0
            i2cdata = i2c.readBytes(0x20, 0xFF, 1)
            i2cdata = (i2cdata[0] & 0xFF) # Convert the data

        channel_ppulse[act_channel] = not (i2cdata & (2 ** act_channel)) and not channel_ppulse_hm[act_channel]
        channel_ppulse_hm[act_channel] = not (i2cdata & (2 ** act_channel))

        if channel_ppulse[act_channel]:
            read_i2c_queue.put(act_channel+1)
            read_i2c_queue.put(datetime.datetime.now())
            read_i2c_queue.put(time.time())

        time.sleep(0.001)

def actual_logging(setpoint, timeset, softwareversion, id, last_pulstime, act_pulstime, channeltype, unit_actual, name1, name2, name3, name4, name5, geo_lat, geo_long):
    act_channel = 1
    act_pausetime = 0
    last_pausetime = 0

    setup_logger('ch_a', '/mnt/sd0/logs/ch_a.log')
    ch_a = logging.getLogger('ch_a')

    while True:
        act_pausetime = time.time() - act_pulstime[act_channel]
        last_pausetime = act_pulstime[act_channel] - last_pulstime[act_channel]

        if act_pausetime < last_pausetime and not last_pausetime == 0:
            act_value = float(timeset[act_channel]) / last_pausetime * float(setpoint[act_channel])
        elif act_pausetime > last_pausetime and not last_pausetime == 0:
            act_value = float(timeset[act_channel]) / act_pausetime * float(setpoint[act_channel])
        else:
            act_value = 0

        if act_value != 0:
            timestamp = str(datetime.datetime.now())
            ch_a.info(timestamp.replace('.',',')+';'+str(softwareversion)+';'+str(id)+';'+str(act_channel)+';s0da;'+str(act_value)+';'+str(channeltype[act_channel])+';'+str(unit_actual[act_channel])+';'+str(name1[act_channel])+';'+str(name2[act_channel])+';'+str(name3[act_channel])+';'+str(name4[act_channel])+';'+str(name5[act_channel])+';'+str(geo_lat[act_channel])+';'+str(geo_long[act_channel]))

            ch_out = configparser.ConfigParser()
            ch_out.read('/opt/config/ch' + str(act_channel) + '_out.ini')
            ch_out['ch' + str(act_channel)]['value'] = str(act_value)
            with open('/opt/config/ch' + str(act_channel) + '_out.ini.new', 'w') as configfile_w:
                ch_out.write(configfile_w)
            os.system('mv -f /opt/config/ch' + str(act_channel) + '_out.ini.new /opt/config/ch' + str(act_channel) + '_out.ini')

        time.sleep(0.4)
        act_channel += 1

        if act_channel == 7:
            act_channel = 1

def main():
    print '***** Startup *****'

    config_channel = 1
    setpoint =  [0,0,0,0,0,0,0]
    timeset =  [0,0,0,0,0,0,0]
    softwareversion = ''
    id = ''
    channelnumber = [0,0,0,0,0,0,0]
    channeltype = [0,0,0,0,0,0,0]
    unit_actual = [0,0,0,0,0,0,0]
    unit_counter = [0,0,0,0,0,0,0]
    name1 = [0,0,0,0,0,0,0]
    name2 = [0,0,0,0,0,0,0]
    name3 = [0,0,0,0,0,0,0]
    name4 = [0,0,0,0,0,0,0]
    name5 = [0,0,0,0,0,0,0]
    geo_lat = [0,0,0,0,0,0,0]
    geo_long = [0,0,0,0,0,0,0]

    last_pulstime = [0,0,0,0,0,0,0]
    act_pulstime = [0,0,0,0,0,0,0]

# CONFIG
    setup = configparser.ConfigParser()
    setup.read('/opt/config/config_out.ini')

    id = setup.get('system', 'id')
    softwareversion = setup.get('software', 'softwareversion')

    while config_channel <= 6:
        setup = configparser.ConfigParser()
        setup.read('/opt/config/ch' + str(config_channel) + '_setup.ini')

        setpoint[config_channel] = setup.get('ch' + str(config_channel), 'setpoint')
        timeset[config_channel] = setup.get('ch' + str(config_channel), 'timeset')

        channeltype[config_channel] = setup.get('ch' + str(config_channel), 'type')
        unit_actual[config_channel] = setup.get('ch' + str(config_channel), 'unit_act')
        unit_counter[config_channel] = setup.get('ch' + str(config_channel), 'unit_count')
        name1[config_channel] = setup.get('ch' + str(config_channel), 'name1')
        name2[config_channel] = setup.get('ch' + str(config_channel), 'name2')
        name3[config_channel] = setup.get('ch' + str(config_channel), 'name3')
        name4[config_channel] = setup.get('ch' + str(config_channel), 'name4')
        name5[config_channel] = setup.get('ch' + str(config_channel), 'name5')
        geo_lat[config_channel] = setup.get('ch' + str(config_channel), 'geo_lat')
        geo_long[config_channel] = setup.get('ch' + str(config_channel), 'geo_long')

        config_channel += 1

# LOGGING
    setup_logger('ch_c', '/mnt/sd0/logs/ch_c.log')
    ch_c = logging.getLogger('ch_c')

    read_i2c_queue = Queue()

    read_process = multiprocessing.Process(target=read_i2c, args=(read_i2c_queue, ))
    read_process.start()

# FILEBEAT
    os.system('kill -9 `pgrep filebeat`')
    time.sleep(1)
    os.system('nice -n 19 /mnt/sd0/filebeat/filebeat -c /mnt/sd0/filebeat/filebeat.use.yml &')

# ACTUAL LOGGING
    thread_actual = threading.Thread(target=actual_logging, args=(setpoint, timeset, softwareversion, id, last_pulstime, act_pulstime, channeltype, unit_actual, name1, name2, name3, name4, name5, geo_lat, geo_long))
    thread_actual.start()

# COUNTER LOGGING
    while True:
        if not read_i2c_queue.empty():
            channel = read_i2c_queue.get()
            datetimestamp = read_i2c_queue.get()
            timestamp = read_i2c_queue.get()
            ch_c.info(str(datetimestamp)+';'+str(softwareversion)+';'+str(id)+';'+str(channel)+';s0dc;'+str(setpoint[channel])+';'+str(channeltype[channel])+';'+str(unit_counter[channel])+';'+str(name1[channel])+';'+str(name2[channel])+';'+str(name3[channel])+';'+str(name4[channel])+';'+str(name5[channel])+';'+str(geo_lat[channel])+';'+str(geo_long[channel]))

            last_pulstime[channel] = act_pulstime[channel]
            act_pulstime[channel] = timestamp

        time.sleep(0.01)

if __name__ == "__main__":
	main()
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Muntlinger: Da Python 2 ja bald sein Lebensende erreicht hat, sollte man schon ein bisschen auf leichte(re) Portierung auf Python 3 achten. Meine Python 2 Programme fangen schon seit längerem mit einem `__future__`-Import an um das Verhalten der Division zu ändern – das Ergebnis ist immer eine Gleitkommazahl – und aus der ``print``-Anweisung eine `print()`-Funktion zu machen. Da müsste man im Code dann das eine ``print`` anpassen, und bei der Division betrifft das wenn ich das richtig sehe nur Code in `actual_logging()` wo die `float()`-Aufrufe bei den Divisionen dann wegfallen können.

Von den Importen wird `importlib` und `Process` aus `multiprocessing` gar nicht benutzt. Weg damit.

Bis auf eine Ausnahme sind alle ``while``-Schleifen eigentlich ``for``-Schleifen bei denen Du mit ``while`` alles manuell und über den Code verteilt machst, was eine ``for``-Schleife in einer Zeile und auf den ersten Blick verständlich macht.

Schau mal in einem Wörterbuch nach was „actual“ und was „current“ auf Deutsch heisst. Du verwendest „actual“ ziemlich sicher falsch. Wenn es richtig wäre, sollte man es nicht mit `act` abkürzen.

Die ganzen ”parallelen” Listen sind nicht gut. Also das man viele Listen hat wo der der Wert am jeweils gleichen Index zu dem Wert in einer anderen Liste an dem Index gehört. Das ist unübersichtlich weil man nicht leicht erkennen kann was denn nun alles zu einem Channel gehört, oder ob eine Liste nicht auch eine andere Bedeutung haben kann als Bestandteil eines Channels zu sein. Das es dann auch noch Listen mit einem Element mehr gibt, wo das erste anscheinend unbenutzt ist und der Index um eins verschoben ist, macht die Sache noch unübersichtlicher. Man muss dann nicht nur wissen welche Listen zu Kanälen gehören, sondern auch noch wo man wenn man das Datum zu Kanal 4 haben will nun 3 und wo 4 als Index verwenden muss.

Ein Kanal drängt sich hier förmlich auf mindestens als Datenobjekt modelliert zu werden und dann *eine* Liste mit Kanalobjekten zu haben in der alle Daten stecken. Dann werden auch einige Aufrufzeilen deutlich kürzer wenn die Daten sinnvoll zu Objekten zusammengefasst werden.

Namen sollte man erst einführen wenn man sie braucht. `config_channel` wird gleich am Anfang der viel zu umfangreichen `main()`-Funktion definiert, aber dann erst viele Zeilen später in der ``while``-Schleife, die eigentlich eine ``for``-Schleife sein sollte, verwendet. Wenn man aus der ``while``-Schleife eine ``for``-Schleife macht, dann vergisst man soweit entfernte Variablen manchmal. Und wenn man den Code mit der Schleife in eine eigene Funktion herausziehen wollte, wäre es auch einfacher wenn der Code der zusammengehört auch ”räumlich” zusammen steht und nicht über die Funktion verteilt ist.

Apropos Variablendefinitionen vergessen: Wofür sollte die Liste `channelnumber` denn mal verwendet werden?

Die Anzahl der Kanäle steckt an ganz vielen Stellen im Code, in Form von literalen Zahlen die entweder die Anzahl oder die Anzahl ±1 als Wert haben und in der Länge der vielen parallel geführten Listen. Das ist unübersichtlich und fehleranfällig bei Änderungen. Wenn man die Anzahl ändert muss man den ganzen Code durchgehen und bei jeder Liste mit 6 oder 7 Elementen überlegen ob die betroffen ist und sie ändern und bei jeder literalen 5, 6, oder 7, ob sie geändert werden muss, oder etwas anderes bedeutet und so bleiben muss. Es sollte eine Konstante `CHANNEL_COUNT` geben und die Länge der Listen sollten von dieser Konstanten abhängen und die literalen Werte von der tatsächlichen Länge der Listen. Dann kann man die Anzahl der Kanäle allein über diese Konstante ändern, ohne irgend etwas anderes in dem Code anfassen zu müssen.

Ähnliches gilt für Pfade. Mindestens der Basispfad zu Log-Dateien etc. sollte nicht mehrfach mitten im Quelltext versteckt sein, sondern einmal am Anfang des Programms als Konstante definiert werden.

Wobei man wahrscheinlich diese ganzen Indexzugriffe und Listen sowieso nicht mehr brauchen wird, wenn man die eine Liste mit den Kanalobjekten hat. Das würde den Code sooo viel einfacher und übersichtlicher machen.

Wenn man nach Aufruf der `setup_logger()`-Funktion immer den gerade eingerichten `Logger` abfragen muss, dann wäre es einfacher wenn die Funktion den Logger den sie ja hat, weil gerade selbst eingerichtet, einfach als Rückgabewert liefert würde. Weder `l` noch `ch_a` und `ch_c` sind gute Namen für Logger. Eigentlich sind das keine guten Namen für irgend etwas. Wofür stehen denn `a` und `c` bei den Loggern? Namen sollen dem Leser sagen was die Werte bedeuten, und keine Rätsel sein. :-)

Zeichenketten und Werte mit `str()` und ``+`` zusammenzustückeln ist kein Python das ist eher BASIC. In Python gibt es Zeichenkettenformatierung mit der `format()`-Methode.

Mir scheint auch das Du `logging` zum schreiben von CSV-Dateien missbrauchst bei denen Du das CSV auch noch selbst von Hand zusammenbastelst. Es gibt ein `csv`-Modul in der Standardbibliothek.

`os.system()` ist schon länger durch das `subprocess`-Modul abgelöst, auf das auch in der Dokumentation zu `os.system()` verwiesen wird. Für `mv` sollte man gar kein externes Programm starten sondern `os.rename()` verwenden, oder falls das nicht möglich ist `shutil.move()`.

In der Shell würde ich keine Backticks mehr verwenden sondern `$(…)`. Also statt 'kill -9 `pgrep filebeat`' besser 'kill -9 $(pgrep filebeat)'. Das ist lesbarer und man kann das im Gegensatz zu Backticks auch verschachteln wenn es sein muss.

Allerdings würde ich versuchen die Shell so wenig wie möglich zu verwenden. Also hier beispielsweise das ``pgrep filebeat``. in einen eigenen Aufruf auszulagern und die Ausgabe dann als Argument für den ``kill``-Aufruf benutzen. Wobei da dann wieder kein externer Aufruf nötig ist, denn es gibt ja `os.kill()` und das `signal`-Modul mit den passenden Konstanten, damit man die ”magische” 9 los wird. Das ``pgrep`` könnte man eventuell auch durch das (externe) `psutil`-Modul loswerden und sich da das Prozessobjekt heraussuchen und mit der entsprechenden Methode beenden.

Ist das überhaupt nötig ``filebeat`` so *drastisch* zu beenden? Reagiert das nicht auf ein SIGTERM? Muss es ein SIGKILL sein?

Das Testen ob die `Queue` leer ist und falls ja eine hundertstel Sekunde zu warten ist Rechenzeitverschwendung. Die `Queue.get()`-Methode blockiert doch so lange bis ein Wert in der Queue ist. Und man sollte davon ausgehen, das sie das ohne „busy waiting“ tut.

Für ein Pulse-Ereignis werden drei Werte in die Queue gesteckt die zusammen gehören. Statt das da beispielsweise ein Tupel mit den Werten reingesteckt wird. Wobei es mir auch nicht sinnvoll erscheint `datetime.datetime.now()` *und* `time.time()` zu übermitteln, weil die doch den gleichen Wert enthalten, mal von der superkurzen Zeitspanne zwischen den beiden Aufrufen abgesehen. Warum machst Du das?

So, jetzt bin ich mit der `main()` durch. Da ist sicher noch mehr, aber für Dich gibt es ja auch erst einmal genug zu tun. :-)

Ich würde sagen erst ”parallele”, zusammenhängende Listen zu einer Liste mit Objekten zusammenfassen und dann ``for``-Schleifen einführen, wären die drängensten Änderungen. Und natürlich Sachen die einfach zu erledigen sind.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Muntliger: die Daten, die zu jedem Kanal gespeichert, sind so umfangreich, dass man ohne eine Strukturierung mit Klassen nicht mehr durchkommt.
Die Funktion wo wirklich geloggt wird (und nicht nur so getan wird als ob) `actual_logging` hat 15 Argumente, viel zu viele. Und nochmal, überall wo actual oder act steht, ist das falsches Englisch, current ist der richtige Begriff. Der Präfix hat auch überhaupt keine Aussagekraft, weil der Wert, den eine Variable gerade hat, ist immer der aktuelle Wert (außer vielleicht bei act_pausetime -> current_pausetime vs. last_pausetime).

Die Funktion `read_i2c` ist etwas verquer. Statt einer while-Schleife brauchst Du zwei Schleifen, eine die das Lesen ständig wiederholt, und eine, die über die Kanäle geht. Wenn man eine Variable mit einem Dummy-Wert belegt, nur dass ein anderer Teil richtig arbeitet, macht man meist etwas falsch. os.nice, sollte wenn überhaupt nur einmal aufgerufen werden.
last_pulsetime und act_pulsetime wird gar nicht benutzt. channel_ppulse wird eigentlich auch nicht benutzt, weil der Wert nur lokal in der Schleife benutzt wird.
Statt drei Werte in eine Queue zu stecken, nimm ein Tuple. Da das Lesen der 6 Channels auf einmal passiert, sollten sie auch den selben Timestamp haben. Warum zweimal die selbe Zeit übertragen, weil wenn Du ein datetime-Objekt brauchst, erstell es einfach aus datetime.datetime.fromtimestamp(timestamp).

Code: Alles auswählen

def read_i2c(read_i2c_queue):
    os.nice(-20) # fast process
    i2c = onionI2C.OnionI2C()
    i2c.write(0x20, [0xFF])
    previous_pulse = [False] * 6
    while True:
        i2cdata = i2c.readByte(0x20, 0xFF)
        timestamp = time.time()
        for channel in range(6):
            pulse = i2cdata & (2 ** channel)
            if not pulse and previous_pulse[channel]:
                read_i2c_queue.put((channel+1, timestamp))
            previous_pulse[channel] = pulse
        time.sleep(0.006)
In `actual_logging` bezweifle ich, das Logging das richtige Mittel ist, es sieht mehr nach dem Schreiben von csv-Dateien aus. Configparser sieht in diesem Anwendungsfall auch komisch aus.
Statt + benutze Stringformatierung, und etwas was man öfter braucht, speichert man in Variablen. Z.b:

Code: Alles auswählen

            ch_out = configparser.ConfigParser()
            ch_out.read('/opt/config/ch' + str(act_channel) + '_out.ini')
            ch_out['ch' + str(act_channel)]['value'] = str(act_value)
            with open('/opt/config/ch' + str(act_channel) + '_out.ini.new', 'w') as configfile_w:
                ch_out.write(configfile_w)
            os.system('mv -f /opt/config/ch' + str(act_channel) + '_out.ini.new /opt/config/ch' + str(act_channel) + '_out.ini')
wird zu

Code: Alles auswählen

    channel = 'ch{}'.format(act_channel)
    config_filename = '/opt/config/{}_out.ini'.format(channel)
    config_filename_new = config_filename + '.new'
    ch_out = configparser.ConfigParser()
    ch_out.read(config_filename)
    ch_out[channel]['value'] = str(act_value)
    with open(config_filename_new, 'w') as configfile_w:
        ch_out.write(configfile_w)
    os.rename(config_filename_new, config_filename)
Und dann gibt es noch eine große Baustelle beim Lesen der Queue. Die ist ja gerade dafür da, nicht ständig nach neuen Werten fragen zu müssen:

Code: Alles auswählen

    while True:
        channel, timestamp = read_i2c_queue.get()
        ch_c.info(';'.join(map(str, [
            datetime.datetime.fromtimestamp(timestamp),
            softwareversion, id,
            channel, 's0dc', setpoint[channel],
            channeltype[channel], unit_counter[channel],
            name1[channel], name2[channel], name3[channel],
            name4[channel], name5[channel],
            geo_lat[channel], geo_long[channel]
        ]))
Das waren jetzt nur ein paar kleine Ausschnitte, weil ich ehrlich gesagt noch nicht durch den ganzen Code durchgestiegen bin. Durch die vielen parallelen Liste und den undurchsichtigen Abhängigkeiten zwischen den Threads, ist das auch schwierig.
Der actual_logging-Thread ist nicht nötig, da dort sowieso nur etwas gemacht wird, wenn was in der Queue war.

Um weiter zu kommen, lerne, wie man Klassen definiert und schreibe den Code damit nochmal neu.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Vielen Dank für eure Tipps - brauchte nun erst mal eine Woche das zu verstehen und anfangen um zu setzen :)

Ich schlag mich noch mit folgendem Problem rum:
Obwohl der Code nur noch zu ca. 20% meine CPU auslastet fehlen mir pro Stunde ca. 1-2 Puls.
Die Pulse generiere ich über eine Siemens SPS - jede Sekunde einen 30ms langen Puls.

Um aus zu schließen, dass die SPS kein Problem hat, hänge ich morgen noch einen Eingang dran und zähle die Pulse dort parallel auch mit,
ich denke aber nicht, dass die Probleme von daher kommen.

Der Rechner ist ein Onion Omega 2+ (580Mhz/64MB RAM).
Denkt ihr Python ist nicht das richtige für das Einlesen? Besser in C einlesen und an Python übergeben?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bleibt die Datenrate so gering? Sind die Pulse immer so lang?
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Datenrate - wird nicht schneller als 1 Pulse/Sekunde werden.
Die Pulse sind nie kürzer wie 30ms.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum machst du das einlesen über I2C statt direkt die GPOIs des PIs?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Beziehungsweise dies Onion-Dings.
Benutzeravatar
Muntliger
User
Beiträge: 40
Registriert: Montag 19. November 2018, 09:09

Dies ist ein Onion Omega.

Der Gedanke war - wenn jemand was falsch anklemmt verabschieden sich "nur" die i2c Bauteile.
Antworten