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 liebes Forum,

ist es möglich, dass man ausgelesene Sensordaten in eine Datei schreiben kann anstatt diese am Bildschirm auszugeben? Wenn ja wie stelle ich das an? Ich bin ein Python Neuling und hobbymäßig dabei mit einem dht11 Sensor, welches ich an einem Raspberry Pi durch ein GPIO Extension Board angeschlossen habe Sensordaten wie Luftfeuchte und Temperatur zu messen. Bisher gelingt es mir jedoch nur die Messwerte am Bildschirm auszugeben. Ich freue mich auf eure Hilfestellung!!
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

und willkommen im Forum.

Natürlich geht das. Vorgehen:
- File-Objekt mit `open()` erzeugen
- die `write`-Methode des File-Objekt nutzen, um Daten rein zuschreiben
- File-Objekt schließen (oder direkt das `with`-Statement bei `open` benutzen, dann erfolgt das Schließen automatisch).

Das ganze ist auch im Python-Tutorial beschrieben (https://docs.python.org/3.5/tutorial/in ... le-objects). Das sollte man so wie so mindestens 1x komplett durchgearbeitet haben.

Außerdem bietet Python mit Bordmitteln noch mehr Möglichkeiten, die Daten zu speichern, z.B. im JSON-Format, als XML-Daten, mit der SQLite Datenbank...

Gruß, noisefloor
BlackJack

CSV sollte man vielleicht noch als Format erwähnen. :-)
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hallo Noisefloor und BlackJack,
vielen Dank für eure Schnelle Rückmeldung. Ich bin nun in der Lage die Sensordaten in eine Textdatei zu übertragen. Ich habe folgendes geschrieben:

Code: Alles auswählen

if humidity is not None and temperature is not None:
      f= open('log.txt', 'a')                                                 # Sensordaten werden in log.txt angelegt
      f.write('Temp={0:0.1f}*C'.format(temperature))        # Temperatur 
      f.write(' ')                                                                # Abstand/Leerzeichen
      f.write('Humidity={0:0.1f}%'.format(humidity))          # Luftfeuchte
      f.write('\n')                                                               # Neue Zeile
      f.close()
else:
 .........Fortsetzung
Nun habe ich mir das mit dem JSON-Format angeschaut aber meine Englisch Kenntnisse sind etwas eingerostet und deswegen hab ich es nicht ganz verstanden. Ich habe ja nun eine Textdatei erstellt die "log.txt" welche die Variablen temperature und humidity enthält. Ist es möglich einfach eine andere Textdatei zu erstellen und die Daten von der log.txt Textdatei in JSON-Format umzuwandeln oder wie lass ich die beiden Variablen sonst in JSON-Format speichern? Danke für eure Unterstützung !!
Zuletzt geändert von Anonymous am Donnerstag 9. Februar 2017, 11:17, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Benutzeravatar
Cronut
User
Beiträge: 34
Registriert: Sonntag 5. Februar 2017, 09:50
Wohnort: HRO, GER

Disclaimer: Ich bin neu in Python und es gibt evtl. viel elegantere/richtigere Lösungen, als meine.

Zunächst würde ich vorschlagen, du erzeugst nach der Messung ein Dictionary und legst da die Werte rein:

Code: Alles auswählen

from datetime import datetime
import json
# [...]
# nach Messung die Daten in Dictionary packen
measurement = {
    'timestamp' : datetime.now().strftime('%Y-%m-%d %H:%M:%S'),  # Messzeitpunkt festhalten
    'temperature' : temperature,
    'humidity': humidity 
}
# dein Dateiöffnungsgedöns, ich nenne die Variable des Files hier mal jsonFile
jsonFile.write(json.dumps(measurement))
# und so weiter
Edit: Aber vielleicht reicht für deinen Zweck auch wirklich nur eine CSV-Datei. Du kannst dich auch mal in Logger einlesen, eventuell macht es Sinn, einen einzusetzen. Ich weiß nicht wie lange du die Messdaten festhalten willst, aber die Datei könnte irgendwann sehr groß werden, da lohnt sich dann vielleicht ein RollingFileAppender (erzeugt ab bestimmter Größe, Datum o.ä. eine neue Datei) zu nutzen.
“Clean code always looks like it was written by someone who cares.” (Michael Feathers)
Check out: https://awesome-python.com/
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hallo Cronut,

erst einmal vielen Dank für deinen Beitrag !! Ich hab jedoch nur ein Verständnis Problem bei Zeile 11. Was genau muss man vor dem :

Code: Alles auswählen

.write(json.dumps(measurement))
anstatt jsonFile eintragen werden und was ist mit Variable des Files gemeint :D ?? Würde mich freuen wenn du mir weiterhelfen kannst. Beste Grüße!!
BlackJack

@Hakan: Eingerückt wird mit vier Leerzeichen pro Ebene.

Die Kommentare sind alle überflüssig, weil die nur sagen was im Code sowieso schon da steht.

noisefloor hatte ``with`` ja schon erwähnt.

Eine Zeile aus vier Schreibzugriffen zusammen zu stückeln ist ein wenig umständlich. Das ginge auch mit einem `write()`-Aufruf, zumal Du die `format()`-Methode ja sowieso schon verwendest.

Warum der '*' für '°'?

Ich lande dann bei so etwas (ungetestet):

Code: Alles auswählen

import io
from datetime import datetime as DateTime

# ...

    if humidity is not None and temperature is not None:
        with io.open('log.txt', 'a', encoding='utf8') as log_file:
            log_file.write(
                u'{0:%Y-%m-%d %H:%M:%S} Temp={1:0.1f}°C Humidity={2:0.1f}%\n'
                    .format(DateTime.now(), temperature, humidity)
            )
Wobei dieses Format nicht so gut ist, zumindest wenn man später noch einmal irgendetwas mit den Daten machen möchte, denn dann muss man die Werte da wieder aus diesen Zeilen mühsam heraus prokeln.

Bei Cronut setzt Zeile 11 voraus das vorher eine Datei zum anfügen geöffnet und an den Namen `jsonFile` gebunden wurde. Das was Du mit `f` machst. Was aber ein bescheidener Name ist weil der dem Leser nicht direkt vermittelt was der Wert bedeutet. Man muss halt raten das `f` für `file` steht, womit man immer noch nicht weiss was das für eine Datei ist. Darum `jsonFile` (per Namenskonvention eigentlich `json_file`) oder wenn man die Bedeutung der Datei noch dazunehmen will `measurements_file` oder `log_file` oder so etwas in der Art.
Hakan
User
Beiträge: 38
Registriert: Mittwoch 8. Februar 2017, 12:13

Hallo BlackJack,

vielen Dank für deine Kritik, Hinweise und Hilfe!! Ich werde mich fürs erste weiter beschäftigen mit meinem Skript und eure tollen Vorschlägen bzw. Hilfestellungen berücksichtigen. Sollte ich die Tage noch offene Fragen haben, freue ich mich erneut von euch zu hören. Jetzt erstmal eine Runde Programmieren :-) JUHUU!
Benutzeravatar
Cronut
User
Beiträge: 34
Registriert: Sonntag 5. Februar 2017, 09:50
Wohnort: HRO, GER

Sorry, wenn ich dich verwirrt habe!
Da gehört quasi das gleiche hin, was du schon geschrieben hast. Bei mir heißt dein "f" halt einfach "jsonFile" (weil ich gerne genaue Bezeichner mag) @BlackJack hatte das schon geschrieben.

Mal ein kurzes Beispiel (um mal den Ablauf zu beschreiben):

Code: Alles auswählen

from datetime import datetime
import json
from random import uniform
from random import randint
from pprint import pprint


jsonData = []

# generiere ein paar Messwerte zum Testen
for i in range(0, 10):
    measurement = {
		'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
		'temperature': ['Celsius', "{0:.2f}".format(uniform(-50.0,50.0))],
		'humidity': ['Percent', "{0:.2f}".format(randint(20, 101))]
	}
    jsonData.append(measurement)

# Ausgeben der erzeugten Messwerte
pprint(jsonData)
print('--------------------------------')


# diese Konstrukt öffnet eine Datei und stellt sicher,
# dass nach dem Zugriff die Datei wieder geschlossen wird
with open('test.json', 'w') as jsonFile:
	jsonFile.write(json.dumps(jsonData, sort_keys = True, indent = 4, ensure_ascii=False))

# Eine neue leere Liste erzeugen
jsonData = []
	
# Datei Zeilenweise einlesen und wieder in die Liste packen
with open('test.json', 'r') as jsonFile:
	jsonData = json.loads(jsonFile.read())

# Ausgeben der eingelesenen Messwerte
pprint(jsonData)
Wie @BlackJack schon geschrieben hat, würde ich auch empfehlen, dass du die Einheiten der Messwerte, von den Messwerten trennst. Das ist in meinem Beispiel auch nicht gut gelöst, da redundant!

Weiteres Problem (meines Wissens nach!): Bei JSON muss die komplette Datei gelesen werden, dann die neuen Daten angehängt werden und dann alles wieder in die Datei geschrieben werden. Ein simples 'anhängen' am Ende der Datei funktioniert nicht.
Falls du dich für CSV entscheiden solltest, dann nutze auf jeden Fall einen eindeutiges und nicht im Datumsformat oder den Messwerten vorkommendes Sonderzeichen als Trennzeichen.
“Clean code always looks like it was written by someone who cares.” (Michael Feathers)
Check out: https://awesome-python.com/
BlackJack

@Cronut: Es gibt JSON Lines, also JSON ohne Zeilenumbruch und dann eine JSON-Serialisierung pro Zeile. Man müsste an das Ergebnis von `json.dumps()` also noch ein '\n' anhängen vor dem wegschreiben. Dann kann man JSON auch zum Protokollieren verwenden ohne die Daten immer wieder einzulesen und zu schreiben.
Benutzeravatar
Cronut
User
Beiträge: 34
Registriert: Sonntag 5. Februar 2017, 09:50
Wohnort: HRO, GER

BlackJack hat geschrieben:[...]
Danke dafür, das kannte ich nicht. Leider kein "Tool" der Standard-Bibliothek.

Hab mal den Hauptteil so umgemodelt:

Code: Alles auswählen

import json_lines

# [...] hier das ganze Gerödel von oben

with open('test.json', 'a') as jsonFile:
	for data in jsonData:
		json.dump(data, jsonFile)
		jsonFile.write('\n')
# Eine neue leere Liste erzeugen
jsonData = []
	
# Datei Zeilenweise einlesen und wieder in die Liste packen
with open('test.json', 'rb') as jsonFile:
	for line in json_lines.reader(jsonFile):
		jsonData.append(line)
Funktioniert soweit super. :)

Dann hab ich festgestellt, dass es wohl eine ?modernere? Version gibt: jsonlines

Code: Alles auswählen

import jsonlines

# [...] hier das ganze Gerödel von oben

with open('test.json', 'a') as jsonFile:
	with jsonlines.Writer(jsonFile) as writer:
		writer.write_all(jsonData)

# Eine neue leere Liste erzeugen
jsonData = []
	
# Datei Zeilenweise einlesen und wieder in die Liste packen
with jsonlines.open('test.json') as reader:
    for obj in reader.iter(type=dict, skip_invalid=True):
        jsonData.append(obj)
Funktioniert auch gut. Muss mal austesten, welches sich "besser" macht. Auf den ersten Blick das Zweite, auch wegen der Typprüfung :)
“Clean code always looks like it was written by someone who cares.” (Michael Feathers)
Check out: https://awesome-python.com/
BlackJack

@Cronut: Ich hätt's mir wahrscheinlich selbst geschrieben. Das lesen wäre ja mit einem ``records = map(json.loads, data_file)`` auch leicht erledigt. (Eventuel + `list()`-Aufruf in Python 3, bzw. `itertools.imap()` in Python 2 wenn man den Iterator möchte.)
Benutzeravatar
Cronut
User
Beiträge: 34
Registriert: Sonntag 5. Februar 2017, 09:50
Wohnort: HRO, GER

BlackJack hat geschrieben:@Cronut: Ich hätt's mir wahrscheinlich selbst geschrieben. Das lesen wäre ja mit einem ``records = map(json.loads, data_file)`` auch leicht erledigt. (Eventuel + `list()`-Aufruf in Python 3, bzw. `itertools.imap()` in Python 2 wenn man den Iterator möchte.)
Das klingt für mich jetzt erstmal kompliziert. Da muss ich erstmal drüber nachdenken, bzw. nachlesen und rumprobieren. Aber danke für den Hint.
“Clean code always looks like it was written by someone who cares.” (Michael Feathers)
Check out: https://awesome-python.com/
BlackJack

Dein Beispiel zum schreiben und lesen wäre ohne zusätzliches Modul das hier (ungetestet):

Code: Alles auswählen

    with open('test.json', 'a') as json_file:
        json_file.writelines(json.dumps(item) + '\n' for item in items)

    with open('test.json') as lines:
        items = list(map(json.loads, lines))
Bei Python 2 kann man beim Lesen den `list()`-Aufruf weg lassen.
Benutzeravatar
Cronut
User
Beiträge: 34
Registriert: Sonntag 5. Februar 2017, 09:50
Wohnort: HRO, GER

Jap, funktioniert wie erwartet, super!
“Clean code always looks like it was written by someone who cares.” (Michael Feathers)
Check out: https://awesome-python.com/
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: 14493
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: 14493
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 :? .
Antworten