Temperatursensorwerte in sqlite

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
johnny_b
User
Beiträge: 6
Registriert: Dienstag 21. April 2020, 10:26

Hallo zusammen,

ich möchte gerne kontinuierlich gemessene Temperaturwerte in eine sqlite-DB schreiben. Es gibt viele Beispiele im Netz mit möglichen Lösungswegen, leider ist es mir aber trotz etlichen Versuchen nicht gelungen auch nur eins davon umzusetzen.
Ein großes Problem für mich als Anfänger ist, dass ich keine Fehlermeldung bekomme. Wenn ich vorher etwas falsch gemacht hatte, konnte ich anhand der Fehlermeldung gezielt an der Behebung arbeiten und mich im Internet schlau machen. Das ist an dieser Stelle leider nicht der Fall.

Was funktioniert:
- Temperatursensor ist funktionsfähig und gibt im 10-s Intervall die gemessenen Werte aus
- SQlite-DB anlegen mit Tabelle und zwei Spalten
- Per Hand Testdaten in die beiden Spalten eingeben und auslesen

Was nicht funktioniert:
- Die Temperatursensorwerte permanent in die DB zu übernehmen


Funktioniert (Temperaturwerte messen):

Code: Alles auswählen

#!/usr/bin/python
# coding=utf-8
# messprogramm.py
# ----------------
 
import os, sys, time

def aktuelleTemperatur():
      
    # 1-wire Slave Datei lesen
    file = open('/sys/bus/w1/devices/28-011317a451b8/w1_slave')
    filecontent = file.read()
    file.close()
 
    # Temperaturwerte auslesen und konvertieren
    stringvalue = filecontent.split("\n")[1].split(" ")[9]
    temperature = float(stringvalue[2:]) / 1000
 
    # Temperatur ausgeben
    rueckgabewert = '%6.2f' % temperature 
    return(rueckgabewert)
 
zeit = time.strftime('%H:%M:%S')
messdaten = aktuelleTemperatur()

try:
    while True:
        # Mit einem Timestamp versehe ich meine Messung und lasse mir diese in der Console ausgeben.
        print ("Temperatur um " + time.strftime('%H:%M:%S') +" beträgt:" + messdaten + " °C")
        # Nach 10 Sekunden erfolgt die nächste Messung
        time.sleep(10)
    
except KeyboardInterrupt:
    # Programm wird beendet wenn CTRL+C gedrückt wird.
    print('Temperaturmessung wird beendet')
except Exception as e:
    print(str(e))
    sys.exit(1)
finally:
    # Das Programm wird hier beendet, sodass kein Fehler in die Console geschrieben wird.
    print('Programm wird beendet.') 

Funktioniert (DB anlegen):

Code: Alles auswählen

import os, sys, sqlite3
      
connection = sqlite3.connect("tempdata.db")
cursor = connection.cursor()


# Tabelle erzeugen
sql = """
CREATE TABLE IF NOT EXISTS tempWerte(
zeit TEXT(20),
temperatur FLOAT(20));"""
cursor.execute(sql)
    
connection.commit()
connection.close()

Funktioniert nicht (Wenn ich das Programm laufen lasse werden die Temperaturwerte wieder angezeigt. Wenn ich dann im Anschluss die DB aber auslese, ist sie leer):

Code: Alles auswählen

import time, sys, sqlite3, Temperaturskript_22

def userTempWerte_db_anlegen():
    
    connection = sqlite3.connect("tempdata.db")
    cursor = connection.cursor()
   
    sql = """INSERT INTO tempWerte VALUES
            (%s,%s)""", (zeit,messdaten)
                               
    cursor.executemany(sql)
    
    connection.commit()
    connection.close()
Ich bin für jeden Tipp dankbar.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das du keine Fehlermeldungen bekommst mag daran liegen, dass du recht grosszuegig try-excepts verstreust, welche die dann wegschlucken, und nur minimale Informationen liefern. Lass die einfach weg.

Und das dein letztes Programm nicht tut ist kein Wunder. Du benutzt die falsche Methode (executemany), benutz execute stattdessen. Und du brauchst dafuer ZWEI Argumente, SQL und Werte, nicht EIN Argument das aus einem Tupel von zwei Werten besteht.

Code: Alles auswählen

cusor.execute(sql, (zeit, messdaten))
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich sehe nirgends, wo Du `userTempWerte_db_anlegen` aufrufst.
Das Programm auf mehrere Dateien aufzuteilen, macht im Moment auch noch keinen Sinn. Da verdeckst Du, dass das Hauptprogramm eigentlich im Untermodul Temperaturskript_22 läuft.

Aktuelle Programme sollte man nicht mehr mit Python2 anfangen, sondern gleich Python3 benutzen.
Strings stückelt man nicht mit + zusammen, sondern benutzt Stringformatierung.
Dateien öffnet man mit dem with-Statement. Variablen und Funktionsnamen schreibt man klein_mit_unterstrich. return ist keine Funktion, die Klammern also überflüssig.
Benutze keine Abkürzungen, auch nicht für SQL-Tabellen. Schreibe temperatur wenn Du Temperatur meinst, und nicht temp.

Der Funktion `userTempWerte_db_anlegen` fehlen Argumente, zumindest zeit und messdaten, wobei das wohl nur eine Temperatur ist.
johnny_b
User
Beiträge: 6
Registriert: Dienstag 21. April 2020, 10:26

Ich danke euch für die Antworten. Leider klappt es auch mit den Hinweisen nicht.

Zum Verständnis: Brauche ich denn wirklich die Methode "userTempWerte_db_anlegen"? Ich habe in einigen Beispielen für den o.g. Anwendungsfall gesehen, dass dort im Grunde das gleiche wie bei mir steht, nur ohne die Deklaration als Methode.

Konkret: Kann die Zeile

Code: Alles auswählen

 def userTempWerte_db_anlegen(): 
nicht einfach weggelassen werden?
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

"klappt nicht" ist schade, aber ohne weiteren Code, was GENAU du probiert hast, kann man da dann auch nur mit den Schulter zucken.

Die Zeile kann weggelassen werden. Dann ist das Programm halt schlechter programmiert. Und bei nahezu allem, das nicht extrem trivial ist, braucht man Funktionen sowieso. Warum ist dir das also so wichtig, die wegzulassen?
Benutzeravatar
__blackjack__
User
Beiträge: 13110
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@johnny_b: Nitpick: Das ist weder eine Deklaration noch eine Methode. Das ist eine Definition einer Funktion.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Demnach ”muss” das dann auch in einer Funktion stehen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
johnny_b
User
Beiträge: 6
Registriert: Dienstag 21. April 2020, 10:26

Ich muss mich korrigieren: Es funktioniert nun doch- zumindest zum Teil. Unterm Strich konnte ich mich an den Fehlermeldungen entlanghangeln, die aufkamen nachdem ich die try-excepts rausgeschmissen habe.

Warum es nur zum Teil klappt: In die Datenbank wird immer nur der erste ausgegebene Zeit- und Sensorwert hinterlegt. Damit bin ich schon ein großes Stück weiter und möchte ich mich nochmals für die Hilfe bedanken. Für heute raucht aber der Kopf etwas und bzgl. des neuen Problems werde ich dann erst einmal selber nach einer Lösung suchen. Vielen Dank nochmal!
johnny_b
User
Beiträge: 6
Registriert: Dienstag 21. April 2020, 10:26

Guten Morgen,
ich bin ein Stück weiter, muss aber leider doch noch einmal nachfragen:

In der Ausgabe verändert sich der Zeit- und Temperaturwert, aber es wird nur das erste Wertepaar in die DB alle 5s übernommen. Die DB füllt sich also immerhin schon einmal kontinuierlich, aber nur mit den gleichen Zeiten und Temperaturen.

Der ursprüngliche Fehler von gestern war, dass ich die DB am Ende immer geschlossen habe. Die Logik ergibt sich mir nicht ganz, da ich in der Schleife die Verbindung ja immer wieder aufbaue. Aber so werden immerhin kontinuierlich Werte in die DB geschrieben. Jetzt dachte ich, dass es möglicherweise mit der Cursor-Verbindung ein Problem geben könnte oder aber mit den Variablen, die ich in dem sql-Ausdruck platziert habe. In vielen Beispielen sind das Angaben wie z.B. %s, allerdings führen die zu Syntax-Fehlern.

Code: Alles auswählen

#!/usr/bin/python
# coding=utf-8
# messprogramm.py
#----------------
 
import os, sys, time, sqlite3
 
 
def aktuelleTemperatur():
      
    # 1-wire Slave Datei lesen
    file = open('/sys/bus/w1/devices/28-011317a451b8/w1_slave')
    filecontent = file.read()
    file.close()
 
    # Temperaturwerte auslesen und konvertieren
    stringvalue = filecontent.split("\n")[1].split(" ")[9]
    temperature = float(stringvalue[2:]) / 1000
 
    # Temperatur ausgeben
    rueckgabewert = '%6.2f' % temperature 
    return(rueckgabewert)
 
zeit = time.strftime('%H:%M:%S')
messdaten = aktuelleTemperatur()

while True :
    # Mit einem Timestamp versehe ich meine Messung und lasse mir diese in der Console ausgeben.
    print ("Temperatur um " + time.strftime('%H:%M:%S') +" beträgt:" + messdaten + " °C")
    
    # Verbindung zur DB herstellen
    verbindung = sqlite3.connect("temperatur.db")
    zeiger = verbindung.cursor()

    #Tabelle erzeugen falls nicht vorhanden   
    sql_anweisung = """
            CREATE TABLE IF NOT EXISTS temperaturwerte (
            zeit TEXT(30), 
            temperatur FLOAT(30));"""

    #Messdaten einfügen
    zeiger.execute(sql_anweisung)

   
    sql = "INSERT INTO temperaturwerte VALUES(?,?);"
                               
    zeiger.execute(sql, (zeit ,messdaten))
       
    verbindung.commit()
    zeiger.close()
                
    # Nach 5 Sekunden erfolgt die nächste Messung
    time.sleep(5)
             
PS: Bitte habt etwas Nachsicht, dass ich die nettgemeinten und richtigen Hinweise wie ordentliche String-Formatierung gerne mitnehme und beherzige, aber ich gerade versuche mich stückweise vorzutasten um eine Lösung zu finden und nicht potentielle neue Fehlerquellen einzubauen. Ich bin wirklich absoluter Anfänger.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das kommt eben davon, wenn man globale Variablen benutzt. Benutz keine globalen Variablen. Und ich bin immer so ein bisschen verwundert ueber dieses "ich mache das nicht, weil ich Anfaenger bin"-Argument. Das du es nicht besser weisst, ist ok. Dass du trotz Hinweis es nicht besser machst sorgt dafuer, dass du dir falsche Vorgehensweisen antrainierst. Woher soll es dann kommen, dass die ploetzlich nicht mehr verwandt werden? Hans, Haenschen, nimmermehr.

Zu deinem Programm:

- pack die while-schleife in eine main-Funktion
- ruf die main-Funktion ganz am Ende auf mit einem __main__-Guard:

Code: Alles auswählen

if __name__ == "__main__":
     main()
- in der while-Schleife musst du sowohl den Zeitstempel, als auch den Temperaturwert, immer wieder abrufen.
- die aktuelleTemperatur (schlecht benannt, sollte aktuelle_temperatur heissen) sollte nur das float zurueck geben. Da schon wieder eine Formatierung vorzunehmen ist falsch, du willst in deinem Programm immer mit korrekten Datentypen und nicht irgendwelchen schon formatierten Strings arbeiten. Formatiert wird erst unmittelbar vor der Ausgabe. Und das ist auch ein Fehler, denn die Datenbank nimmt und soll ja auch ein Float bekommen.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Tabellen werden ein mal beim Initialisieren einer Datenbank angelegt, das sollte nicht jedesmal! beim Eintragen einer Zeile passieren.
`zeit` ist immer die Zeit zu der das Programm gestartet wurde. `messdaten` enthält die Temperatur zum Start des Programms.
Statt des low-level-Aufrufs time.strftime solltest Du auch das datetime-Modul verwenden.
johnny_b
User
Beiträge: 6
Registriert: Dienstag 21. April 2020, 10:26

Sirius3 hat geschrieben: Mittwoch 22. April 2020, 11:01 Tabellen werden ein mal beim Initialisieren einer Datenbank angelegt, das sollte nicht jedesmal! beim Eintragen einer Zeile passieren.
Habe ich das nicht durch den "IF NOT EXISTS"-Ausdruck ausgehebelt? Ansonsten würde ja nur noch übrig bleiben, die Initialisierung auszulagern. Das hatte ich ursprünglich auch so geplant, habe das aber aufgrund eines Hinweises hier im Thread zusammengefügt.
__deets__ hat geschrieben: Mittwoch 22. April 2020, 10:53 Das kommt eben davon, wenn man globale Variablen benutzt. Benutz keine globalen Variablen. Und ich bin immer so ein bisschen verwundert ueber dieses "ich mache das nicht, weil ich Anfaenger bin"-Argument. Das du es nicht besser weisst, ist ok. Dass du trotz Hinweis es nicht besser machst sorgt dafuer, dass du dir falsche Vorgehensweisen antrainierst. Woher soll es dann kommen, dass die ploetzlich nicht mehr verwandt werden? Hans, Haenschen, nimmermehr.
Bitte versteh mich nicht falsch, ich bin dir wirklich dankbar für die Hinweise, da sie mich bisher maßgeblich weitergebracht haben. Und ich weiß auch, was du mir sagen möchtest und ich habe auch in dem Post, auf das du dich beziehst, geschrieben, dass ich sie mitnehme und beherzigen möchte. Python und die dazugehörigen Strukturen sind aber wirklich nicht ohne, wenn man kaum Berührungspunkte bisher damit hatte. Ich habe gerade das Tutorial von python-lernen.de durchgeklickt und habe mir nun etwas halbwegs sinnvolles suchen wollen, um überhaupt mal ein Gefühl dafür zu bekommen, wie man mit Python arbeitet. Dann kommt es irgendwann zu der Situation, dass man nicht weiterkommt und nach Beispielen im Internet sucht und die dann auf seinen Anwendungsfall anzupassen. Das werden dann sehr schnell sehr viele Baustellen. Ich versuche es besser zu machen.
__deets__ hat geschrieben: Mittwoch 22. April 2020, 10:53
- in der while-Schleife musst du sowohl den Zeitstempel, als auch den Temperaturwert, immer wieder abrufen.
Meinst du damit, dass ich deswegen die main()-Funktion einfügen soll, oder soll ich den Zeitstempel und Temperaturwert in der Schleife noch an zusätzlichen Stellen platzieren? Das wäre mir nicht klar.
__deets__ hat geschrieben: Mittwoch 22. April 2020, 10:53
- die aktuelleTemperatur (schlecht benannt, sollte aktuelle_temperatur heissen) sollte nur das float zurueck geben. Da schon wieder eine Formatierung vorzunehmen ist falsch, du willst in deinem Programm immer mit korrekten Datentypen und nicht irgendwelchen schon formatierten Strings arbeiten. Formatiert wird erst unmittelbar vor der Ausgabe. Und das ist auch ein Fehler, denn die Datenbank nimmt und soll ja auch ein Float bekommen.
Du meinst dieses float

Code: Alles auswählen

temperature = float(stringvalue[2:]) / 1000
verwenden und den Teil

Code: Alles auswählen

    # Temperatur ausgeben
    rueckgabewert = '%6.2f' % temperature 
   
auch in die main()-Funktion aufnehmen?
Benutzeravatar
__blackjack__
User
Beiträge: 13110
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@johnny_b: `os` und `sys` werden importiert, aber nirgends verwendet.

Neben der Schreibweise von `aktuelleTemperatur`, die __deets__ ja schon angesprochen hat, ist der Name inhaltlich auch nicht gut, denn das wäre ein guter Name für die aktuelle Temperatur. Also den Wert, den die Funktion liefert. Aber kein guter Name für die Funktion selbst. Funktionen werden üblicherweise nach der Tätigkeit benannt die sie durchführen. Also hier beispielsweise `lese_temperatur()` oder `read_temperature()`.

Dateien sollte man wo es geht mit der ``with``-Anweisung öffnen. Und bei Textdateien immer eine Kodierung angeben. Wenn man den gesamten Dateiinhalt lesen will, braucht man sich mit `pathlib.Path` weder um das öffnen noch das schliessen der Datei kümmern, denn diese Objekte haben eine Methode die das alles erledigt.

`messdaten` ist falsch weil es sich nicht um Daten (Mehrzahl) handelt, sondern nur im ein Messdatum. Es wäre aber noch besser wenn man am Namen ablesen könnte *was* da gemessen wurde: die Temperatur.

Faustregel für Kommentare: Die beschreiben nicht *was* der Code macht, denn das steht da ja bereits als Code, sondern warum er das so macht. Sofern das nicht offensichtlich ist. Und offensichtlich sind auch Sachen die in der Dokumentation der Programmiersprache oder der verwendeten Bibliotheken stehen.

Die Verbinung würde man eher einmal vor der Schleife erstellen, statt immer wieder neue Verbinungen für jeden Messwert, die zudem nicht sauber geschlossen werden. `sqlite3.Connection`-Objekte sind auch Kontextmanager, können also mit der ``with``-Anweisung verwendet werden. Das Erstellen der Tabelle gehört, wie Sirius3 schrieb, auch vor die Schleife.

Der Zeitstempel ist kein TEXT(30) sondern ein TIMESTAMP und die (30) bei FLOAT(30) macht keinen Sinn.

Den Cursor `zeiger` zu nennen ist IMHO falsch. SQLs CURSOR könnte man so bezeichnen, aber die DB-API V2 `Cursor`-Objekte haben nicht wirklich etwas mit SQLs CURSOR zu tun. Und im Programm ”zeigt” das Objekt ja auch nie auf irgend etwas. Dieses Bild funktioniert ja eher nur bei SELECTs.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import sqlite3
import time
from contextlib import closing
from datetime import datetime as DateTime
from pathlib import Path


def read_temperature():
    return (
        float(
            Path("/sys/bus/w1/devices/28-011317a451b8/w1_slave")
            .read_text(encoding="ascii")
            .split("\n")[1]
            .split(" ")[9][2:]
        )
        / 1000
    )


def main():
    with sqlite3.connect("temperatur.db") as verbindung:
        with closing(verbindung.cursor()) as cursor:
            cursor.execute(
                """CREATE TABLE IF NOT EXISTS temperaturwerte (
                    zeit TIMESTAMP,
                    temperatur FLOAT
                )"""
            )
        
        while True:
            zeit = DateTime.now()
            temperature = read_temperature()
            
            print(
                f"Temperatur um {zeit:%H:%M:%S} beträgt {temperature:5.2.f} °C"
            )
            
            with closing(verbindung.cursor()) as cursor:
                cursor.execute(
                    "INSERT INTO temperaturwerte VALUES(?,?)",
                    (zeit, temperature),
                )
                verbindung.commit()

            time.sleep(5)


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
johnny_b
User
Beiträge: 6
Registriert: Dienstag 21. April 2020, 10:26

Vielen Dank für den Ansatz. Das hat enorm geholfen, um die vielen Hinweise nachzuvollziehen und ich weiß nun auch, was mit "schlechter programmiert" gemeint ist. Das sieht so in der Form deutlich eleganter aus, obwohl die Funktionalitäten im Großen und Ganzen ähnlich sind.

In der while-Schleife gab es an dieser Stelle einen attribute __exit__-Fehler:

Code: Alles auswählen

with closing(verbindung.cursor()) as cursor:
den ich so lösen konnte:

Code: Alles auswählen

cursor = verbindung.cursor()
Ich habe aber leider nicht nachvollziehen können, warum der cursor mit dem closing-Aufruf geladen wird. Wenn ich danach z.B. cursor.execute() ausführe, hängt doch das closing immer dadran. Ist das nicht ein Widerspruch, wenn man beispielsweise die Tabelle anlegen möchte? Oder bedeutet das, dass nach dem Befehl immer "closing" ausgeführt wird?
Benutzeravatar
__blackjack__
User
Beiträge: 13110
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@johnny_b: Das darf kein `AttributeError` kommen. Das ist Bestandteil der Standardbibliothek und ist genau dafür da das es unter anderem die `__exit__()`-Methode gibt.

Der `Cursor` soll geschlossen werden, darum ``with`` und `closing()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten