Mit Python Sensordaten in eine Datei schreiben/speichern

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.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hallo ihr Lieben,

ich hab noch eine offene Frage! Bisher habe ich die Messwerte in JSONFORMAT in eine Textdatei abgespeichert. Danach habe ich mein Skript so erweitert, dass der gemessene Sensorwert in JSONFORMAT in eine Textdatei abgespeichert wird und hinterher diesen einen Sensorwert in JSON Format an eine Plattform gesendet(mittels POST requests). Es kann aber natürlich vorkommen, dass die Internetverbindung abbricht. In dem Fall habe ich eine Exception eingebaut, sodass mein Skript noch weiterläuft und eine kleine Pause macht. Nun kann ich ja lokal weiter messen und die Messwerte in eine Textdatei abspeichern. Wie kann ich jedoch, in dem Fall wo ich keine Internetverbindung habe, die nicht hoch geladenen Messwerte welche lokal in der Textdatei sind, nachträglich absenden? Ich hoffe ihr könnt meine Frage nachvollziehen und freue mich erneut auf Hilfestellungen und Tipps!!
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich verstehe die Frage nicht. Wenn du eine Datei erzeugt hast, und Code hast, der diese irgendwie weiterverarbeitet - was hindert dich daran, diese Verarbeitung *nochmal* vorzunehmen?
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hi @ __deets__,

also folgendes Szenario:
1. Skript wird ausgeführt
2. Sensor erfasst einen Messwert (Messwert 1)
3. Dieser Messwert wird als JSON Format in eine Textdatei geschrieben
4. Aus der Textdatei wird dieser Messwert gelesen und
5. anschließend an eine Plattform gesendet mittels POST request(HTTP)
6. Happy End alles klappt wunderbar
nächstes Szenario geht davon aus, dass ich während der Laufzeit die Internet Verbindung trenne:
1. Skript wird ausgeführt
2. Sensor erfasst nächsten Messwert (Messwert 2)
3. Dieser Messwert wird als JSON Format in eine Textdatei geschrieben
4. Aus der Textdatei wird dieser Messwert gelesen und
5. anschließend an eine Plattform gesendet mittels POST request(HTTP)
6. Da keine Verbindung kommt meine eingebaute Fehlermeldung (exception connection error....)
7. Pause
8. Wiederholung ab Schritt 1

Also wenn nach Schritt 8 die Internetverbindung wieder besteht, schickt er den 3. Messwert. Ich will aber, dass er den Messwert 2 auch berücksichtigt und erneut hochladet. Ich hoffe jetzt ist es etwas verständlicher vielleicht hab ich auch ein blöden Denkfehler. Hilfeee :? :D
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der Fehler ist doch schon in deinem Ansatz, ein Skript zu starten und wieder zu stoppen. Eine Messwertaufnahme erfolgt doch kontinuierlich, wenn du Werte nicht ablieferen kannst & dann alle gesammelten Daten seit dem letzten erfolgreichen mal abschicken moechtest, sollte das ganze doch einfach so aussehen:

Code: Alles auswählen

werte = []
while True:
     warte_bis_zum_naechsten_messzeitraum()
     werte += sammeleWerte()
     try:
     	verschickeWerte(wert)
     	werte = [] # erfolgreich abgeschickt, also werte leeren
     except Fehler:
        print("ein Fehler trat auf, versuche es wieder")
Das ganze so ans laufen zu bringen, dass du es mit diskreten Skript-Starts handhaben kannst, die dann zB durch die Existenz von Dateien feststellen, was zu tun ist, ist natuerlich auch moeglich, aber unnoetig kompliziert. Vor allem waere dann auch JSON niemals das Format meiner Wahl, sondern gleich eine vernuenftiges Backend in Form einer SQLite Datenbank oder ggf. einer persistenten Queue.
Zuletzt geändert von Anonymous am Dienstag 7. März 2017, 14:50, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

@__Deets__

ich glaub ich hab mich eben falsch ausgedrückt. Der Skript wird nur einmal gestartet es wiederholt sich eine Funktion die diese Aufgaben wie von mir eben beschrieben enthält :D . Bei deiner Vorgehensweise würde ich ja die Liste leeren beim erfolgreichen abschicken. Was ich will ist aber, dass die Einträge in die Textdatei von Zeile zu Zeile hochgeladen werden. Diese Textdatei darf nicht geleert werden, sie ist ein lokaler log File. Bei einem Connection Error werden die Messwerte weiter in die Textdatei eingetragen nur nicht hochgeladen. Nachdem die Verbindung wieder besteht überspringt er die Messwerte die nicht hochgeladen wurden <----Da ist mein Problem. Ich will nun aber, dass er ab der Zeile hochlädt, wo er nicht mehr hochladen konnte. Bisher ist es aber so, dass ich nur den neusten Wert hochlade. Schwierige Angelegenheit :? .
BlackJack

@Hakan: Du vermischt hier zwei Dinge die nichts miteinander zu tun haben: das Protokollieren und das Hochladen. Für's protokollieren kannst Du einfach in die Datei schreiben. Das hochladen sollte nichts mit der Datei zu tun haben. Dafür würde ich die Messwerte einfach in eine Queue stecken und der Code zum hochladen holt sich da die Werte raus und lädt sie hoch. Wenn es geht. Wenn nicht wartet er bis es wieder geht und macht dann einfach weiter.

Falls die Daten die hochgeladen werden persistent sein müssen, um beispielsweise auch einen Programmabsturz oder Neustart zu überleben, würde ich eine Datenbank zum zwischenspeichern verwenden. Da kommt es dann darauf an, ob alle Daten dort verbleiben sollen, dann könnte man eine zusätzliche Tabelle anlegen mit den IDs der Datensätze die noch nicht hochgeladen wurden. Ansonsten löscht man einfach die, die bereits hochgeladen wurden, wieder aus der Datenbank.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

@__Deets__ , BlackJack

danke dass ihr euch die Zeit genommen habt meine Frage zu beantworten. Ich finde die Idee von BlackJack echt super :D und werde versuchen das gleich umzusetzen. Dass ich nicht selbst darauf komme aber ärgert mich etwas aber als Neuling weiß man ja auch nicht unbedingt was alles möglich ist...Im diesen Sinne danke euch beiden und einen schönen Nachmittag!!
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hallo liebes Forum,

ich bin ziemlich verwirrt wie ich weiter vorgehen soll :K . Folgenden Quellcode habe ich bisher geschrieben:

Code: Alles auswählen

#!/usr/bin/env python
import RPi.GPIO as GPIO
import Adafruit_DHT as DHT
from datetime import datetime as DateTime
import json
import time
import pytz
import urllib2
import requests
import thread
import json_lines
from pprint import pprint
global jsonData
Sensor = 11
humiture = 17
def setup():
	print('Setting up, please wait...')
def loop():
        humidity, temperature = DHT.read_retry(Sensor, humiture)
        if humidity is not None and temperature is not None:
            jsonData = []
            measurement = {
                    'time': DateTime.isoformat(DateTime.now(pytz.timezone('Europe/Berlin'))),
                    'type':"c8y_demo",
					'source':{"id":"4420437"} ,
                    'Messung_der_Temperatur': {"T":{
                    "value": temperature, "unit":"C"}},
                    "Messung_der_Luftfeuchte":{"%":{
                    "value": humidity, "unit":"%"}}
            }
            jsonData.append(measurement)
            pprint(jsonData)	
            print('--------------------------------')
            global new
            new = json.dumps(measurement)
            with open('test.json', 'a') as jsonFile:
                for data in jsonData:
                    json.dump(data, jsonFile)
                    jsonFile.write('\n')
                    jsonData = []
            with open('test.json', 'rb') as jsonFile:
                for line in json_lines.reader(jsonFile):
                    jsonData.append(line)
        else:
            print("Failed to get reading!")
            
def send():
        global items
        items = new
        url = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        payload = items
        headers = {
                'authorization': "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                'content-type': "application/json",
                'accept': "application/vnd.com.nsn.cumulocity.measurement+json",
                'cache-control': "no-cache",
        }
        response = requests.request("POST", url, data=payload, headers=headers)
        if response.status_code == 201:
            print(str(response.status_code) + "Measurement created")
            #Intervall der Messungen festlegen in Sekunden! Differenz s - 2,035sec
            time.sleep (57.965)
        else:
            print("fehlgeschlagen")
def check():
    try:
        urllib2.urlopen('http://216.58.192.142', timeout=1)
        return True
    except urllib2.URLError as err: 
        return False   
def sendagain():
    if check() is True:
        send()
    else:
        print("Senden fehlgeschlagen da keine Internetverbindung besteht!")
        time.sleep( 10 )
        sendagain()
def destroy():
	GPIO.cleanup()
def main():
    if __name__ == "__main__":
            setup()
            i = 1
            while i > -1:
                try:
                    loop()
                    check()
                    if check() is True:
                        send()
                    else:
                        print("Keine Internetverbindung vorhanden")
                        thread.start_new_thread(sendagain,  ())
                        time.sleep ( 57.965 )
                except KeyboardInterrupt:
                    print("Exit")
main()
Was nach print("Keine Internetverbindung vorhanden") passieren soll ist folgendes. Er soll einen neuen thread starten und versuchen den Messwert abzuschicken, denn er nicht hochladen konnte. Was stattdessen passiert ist, dass er sobald er wieder am Internet verbunden ist, einfach den neuesten Messwert mehrmals sendet :oops: . Wie kann ich denn von Funktion zu Funktion Parameter übertragen, ohne dass die Werte geändert werden oder gibt es eine möglichkeit dem thread sendagain() ein festen Parameter items zuzuweisen, denn er in die send() Funktion einfügt? Also das wird für mich hier etwas zu schwierig eine Lösung zu finden. Tipps Vorschläge??
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Hakan: thread sollte nicht verwendet werden und wurde durch threading ersetzt. Das was Du da geschrieben hast, ist nur eine Aneinanderreihung von Code-Fragmenten die in ihrer Zusammensetzung nicht wirklich Sinn ergeben. global auf Modulebene macht keinen Sinn und in Funktionen sollte man es nicht verwenden. Funktionen sollten alles was sie brauchen, über ihre Argumente bekommen und haben normalerweise Rückgabewerte, mit denen man was machen sollte. Der Aufruf von check() in main ist also überflüssig, wie die ganzen Auffrufe von check, da es nicht garantiert, dass eine Millisekunde später nicht doch der eigentliche HTTP-Request fehlschlägt. Da sollte man schon in send() eine richtige Fehlerbehandlung machen.
Die Funktion loop() ist keine Schleife, so ein verwirrender Name schadet mehr, als er nützen könnte. Die Verstreuten sleep-Aufrufe in send() bzw. main() sorgen nur dafür, dass niemand verstehen kann, wo tatsächlich auf was gewartet wird. Wenn Du wirklich exakt jede Minute eine Messung haben möchtest, mußt Du das sowieso anders angehen. In sendagain() hast Du eine Rekusion, die kein Ersatz für eine Schleife sein sollte.
Das if __name__ in main() sollte außerhalb stehen. Die while-Schleife ist eine while-True, da sich i nie ändert. Dass man das Programm nicht mit STRG+C abbrechen kann ist wohl nicht gewollt, denn die print-Anweisung suggeriert etwas anderes.

Ich empfehle folgende Schritte:
1. alles Löschen.
2. überlege Dir, was Du alles machen willst, was parallel ablaufen sollte, was regelmäßig passieren soll.
3. schreibe Funktionen, die nur eine Aufgabe erledigen
4. wenn Du eigene Funktionen für Messwert lesen, Datei schreiben und Senden hast, können wir gemeinsam überlegen, wie man das alles zusammensetzen kann.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hallo @Sirus3,
alles löschen :cry: . Die Idee gefällt mir als Anfänger überhaupt nicht aber ich will mir ja auch keinen schlechten Programmierstil angewöhnen (wie er hier wahrscheinlich vorliegt) und nehme deine hilfreichen Tipps mit und versuch das ganze durchdachter anzugehen. Eine Sache verstehe ich aber nicht. Du sagtest: "Funktionen sollten alles was sie brauchen, über ihre Argumente bekommen und haben normalerweise Rückgabewerte, mit denen man was machen sollte." . Ich verarbeite aber letztendlich einen Messwert von Funkton zu Funkton. Ist das so schlecht? Beziehungsweise wie soll ich das sonst umsetzen? In jeder Funktion eine neue Messung vornehmen würde ja keinen sinn ergeben ....
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Code: Alles auswählen

def mache_messung():
      # ... tu was um messwert zu ermitteln, und den zeitstempel
      return messwert, zeitstempel      
      
def verarbeite_messwert(messwert):
      # mach was mit dem messwert
      
      
def arbeite():
      while True:
             messwert, zeitstempel = mache_messung()
             print(zeitstempel)
             verarbeite_messwert(messwert)
Das ist alles was Sirius3 gemeint hat. Funktionen bekommen Parameter die sie fuer ihre Aufgabe brauchen, und liefern Ergebniss mit return zurueck (da gehen auch mehrere, wie du siehst).

Faustregel: niemals global verwenden. Wirklich. Es geht.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hi @ __Deets__,

danke für dein Beispiel. Alles klar ich verwende kein Global mehr!! Ich meld mich dann morgen wieder wenn ich das ganze umgesetzt habe! Gibt es in diesen Forum eig. eine Kaffekasse :-) eure Tipps sind mir sehr hilfreich!

Beste Grüße :D
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hallo Zusammen,

Was will ich machen?
- Mit Sensoren die Luftfeuchtigkeit und Temperatur messen
- Messung in eine Textdatei abspeichern
- Messung an eine Platform senden

Was sollte regelmäßig ablaufen?
- Es soll minütlich oder im 5 Minuten Takt gemessen werden
- Diese Messwerte werden verarbeitet

Was sollte parallel ablaufen?
- Die Messungen sollten in eine Textdatei abgespeichert werden und parallel versendet werden können(wobei beides unabhängig voneinander ablaufen soll)
- Bei Konnektivitätsproblemen sollten die Messungen fortgeführt werden und hinterher parallel versendet werden (Sobald wieder eine Internetverbindung besteht)

Mögliche Fehler:
- Sensor nicht richtig angeschlossen (keine Messwerte möglich)
- Keine Internetverbindung ( Time out, Connection Error)
- Textdatei zu voll (nach einer sehr langen laufzeit)
- Seiteneffekte

Code: Alles auswählen

#!/usr/bin/env python
import RPi.GPIO as GPIO
import Adafruit_DHT as DHT
from datetime import datetime as DateTime
import json
import time
import pytz
import urllib2
import requests
import thread
import json_lines
from pprint import pprint

def Einleitung():
	print('Setting up, please wait...')
def Messung_vornehmen():
    Sensor = 11
    humiture = 17
    humidity, temperature = DHT.read_retry(Sensor, humiture)
    timestamp = DateTime.isoformat(DateTime.now(pytz.timezone('Europe/Berlin')))
    return humidity,  temperature,  timestamp
def Messung_Dictionary(humidity,  temperature,  timestamp):
    measurement = {
                    'time': timestamp,
                    'type':"c8y_demo",
					'source':{"id":"4420437"} ,
                    'Messung_der_Temperatur': {"T":{
                    "value": temperature, "unit":"C"}},
                    "Messung_der_Luftfeuchte":{"%":{
                    "value": humidity, "unit":"%"}}
    }
    return measurement
def erstelle_Liste(measurement):
    jsonData = []
    jsonData.append(measurement)
    pprint(jsonData)
    print("---------------------------------")
    return jsonData
def schreibe_Textdatei(jsonData):
    with open('test.json', 'a') as jsonFile:
        for data in jsonData:
            json.dump(data, jsonFile)
            jsonFile.write('\n')
            jsonData = []
    with open('test.json', 'rb') as jsonFile:
        for line in json_lines.reader(jsonFile):
            jsonData.append(line)
def senden(measurement):
        try:
            items = json.dumps(measurement)
            url = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            payload = items
            headers = {
                'authorization': "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                'content-type': "application/json",
                'accept': "application/vnd.com.nsn.cumulocity.measurement+json",
                'cache-control': "no-cache",
            }
            response = requests.request("POST", url, data=payload, headers=headers)
            print(response.text)
            print(str(response.status_code))
        except requests.ConnectionError:
            print("Keine Internetverbindung vorhanden")
def arbeite():
    # Dieser Teil muss noch angepasst werden!!
    while True:
        humidity,  temperature,  zeitstempel = Messung_vornehmen()
        measurement = Messung_Dictionary(humidity,  temperature,  zeitstempel)
        jsonData = erstelle_Liste(measurement)
        schreibe_Textdatei(jsonData)
        senden(measurement)
arbeite()
Ich hoffe ich hab die Funktionen jetzt richtig umgesetzt. Freue mich auf eure Tipps. MFG
Zuletzt geändert von Anonymous am Dienstag 21. März 2017, 10:14, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Hakan: das sieht doch schon mal ganz gut aus. Funktionen werden normalerweise nach Tätigkeiten benannt, wobei nur Messung_Dictionary davon abweicht. Diese verschachtelten Wörterbücher sehen auch seltsam aus. Hast Du Dir das Datenformat ausgedacht? schreibe_Textdatei erhält eine einelementige Liste von erstelle_Liste während senden direkt das Wörterbuch erhält. Warum? In schreibe_Textdatei liest Du die Daten auch wieder, um sie danach wegzuschmeißen. Warum? Die Einrückungen stimmen noch nicht. Leerzeilen zwischen den Funktionen erhöhen die Lesbarkeit.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

@Sirius3: Das Datenformat habe ich mir nicht ausgedacht. Die Plattform an der ich die Daten versende erwartet dieses Datenformat. Ich fand es praktisch den Payload erst als Dictionary zusammen zu fassen und hinterher in senden() per Einzeiler in das JSON Format zu ändern. In schreibe_Textdatei werden die Messungen in eine Textdatei von Zeile zu Zeile übertragen. Wenn er die nicht hinterher wegschmeißt würde ich ja doppelte Einträge aus erstelle_liste haben oder seh ich das falsch? Er soll ja aber bei jeden Durchlauf aus der Liste den aktuellsten Eintrag in die Textdatei übertragen und nicht die komplette liste!!
BlackJack

@Hakan: Du hast doch im Programm gar keine Liste mit allen Messungen ausser der die Du unsinnigerweise einliest und dann nichts damit machst. Und die Liste mit *einer* Messung ist halt auch nicht sinnvoll. `schreibe_textdatei()` könnte/sollte einfach so aussehen:

Code: Alles auswählen

def schreibe_textdatei(json_data):
    with open('test.json', 'a') as json_file:
        json.dump(json_data, json_file)
        json_file.write('\n')
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

@BlackJack: tatsächlich du hast Recht. Da bin ich wohl durcheinander gekommen als ich noch die ganzen Fragmente zusammengesetzt habe. Ich hab das jetzt so übernommen!
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Guten morgen liebe Forenmitglieder,

nun hab ich ja alle wichtigen Funktionen deklariert. Jetzt stellt sich die große Frage wie ich das ganze zusammensetzen kann. Ich wüsste zum Beispiel nicht ganz wie man das lösen könnte, wenn man keine Internetverbindung mehr hat. In die Exception allein springen hilft mir ja nicht wirklich weiter. Ich dachte mir dass man das vielleicht so handhaben kann, dass man ein Hauptprogramm hat welches immer läuft egal was passiert und Unterprogramme gestartet werden, falls ein Fehler auftritt. Habt ihr eine Idee wie ich das umsetzen könnte oder was ich mir anschauen sollte um dieses Problem zu lösen?
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Hakan: es laufen verschieden Dinge bei Dir parallel ab, und so würde ich das dann auch Programmieren, mit Threads: ein Mess-Thread liefert Daten und steckt sie in Queue für das Schreiben in eine Datei und in Queue für das Senden übers Netz. Ein Schreib-Thread wartet auf neue Messungen aus der Queue und schreibt sie, wenn sie da sind. Ein Netz-Thread wartet auf neue Messungen wenn sie da sind und versucht sie zu verschicken. Falls das nicht geht, wartet er eine gewisse Zeit und versucht es dann erneut. So hast Du drei unabhängige Schleifen, die unabhängig auf Fehler regieren können.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

@Sirius3: Da kenn ich mich noch nicht so gut mit aus mit Queues und Threads :K und wenn ich mir paar Beispiele anschaue im Internet sieht es so aus als ob ich nicht drum rum komme, ohne Klassen oder eine Klasse zu erzeugen :K . Seh ich das richtig? Dann würde ich nähmlich ab Klassen erstmal anfangen zu lernen!!

Beste Grüße
Antworten