Das leidige DHT22 Thema. Zwei DHT22, Werte nebeneinander aber wie?

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Bierschors
User
Beiträge: 3
Registriert: Sonntag 16. Oktober 2022, 11:20

Hallo zusammen,

kurz und knapp, ich habe 2 DHT22 an meinem Raspi hängen. Für jeden habe ich ein eigenes Script. Habe die zwei in einem verpackt.

Ausgegeben wirds jetzt so:

H;16.10.2022;12:23;67.9;20.5
R;16.10.2022;12:23;80.9;19.4
H;16.10.2022;12:28;67.4;20.6
R;16.10.2022;12:28;80.8;19.7
usw.

H/R =Heizung/Raum;Datum;Uhrzeit;Luftfeuchtigkeit;Temperatur

So, mein "Problem" ist, das die Werte untereinaner sind. Besser, die Werte zu verarbeiten wäre, wenn sie nebeneiander wären (für mich).

H;16.10.2022;12:23;67.9;20.5;R;16.10.2022;12:23;80.9;19.4
H;16.10.2022;12:28;67.4;20.6;R;16.10.2022;12:28;80.8;19.7
usw.

Ich komme ums verrecken nicht drauf, wie ich das hinbekomme. Ja, ich weiß, Googel ist dein Freund, lerne Python, ....
Habe gerade damit angefangen, bräuchte nur einen kurzen Denkanstoß.

Danke schonmal im voraus.

Code: Alles auswählen

import time
import datetime
import board
import adafruit_dht

# dht D4 und D17 initialisieren:
dhtDevice = adafruit_dht.DHT22(board.D4)
dhtDevice2 = adafruit_dht.DHT22(board.D17)

while True:
    try:
        # Ausgabe
        temperature_c = dhtDevice.temperature
        temperature_f = temperature_c * (9 / 5) + 32
        humidity = dhtDevice.humidity
        now = datetime.datetime.now()
        date_time_str = now.strftime("%d.%m.%Y;%H:%M")
        print(
            "H;{};{:.1f};{}".format(
                date_time_str, humidity, temperature_c
            )
        )

        temperature_c = dhtDevice2.temperature
        temperature_f = temperature_c * (9 / 5) + 32
        humidity = dhtDevice2.humidity
        now = datetime.datetime.now()
        date_time_str = now.strftime("%d.%m.%Y;%H:%M")
        print(
            "H;{};{:.1f};{}".format(
                date_time_str, humidity, temperature_c
            )
        )
                
    except RuntimeError as error:
        # Bei Auslesefehler einfach weitelesen
        print(error.args[0])
        time.sleep(300.0)
        continue
    except Exception as error:
        dhtDevice.exit()
        raise error

    time.sleep(300.0)
Was muss zwischen den beiden Codeschnippseln rein?

Code: Alles auswählen

temperature_c = dhtDevice.temperature
        temperature_f = temperature_c * (9 / 5) + 32
        humidity = dhtDevice.humidity
        now = datetime.datetime.now()
        date_time_str = now.strftime("%d.%m.%Y;%H:%M")
        print(
            "H;{};{:.1f};{}".format(
                date_time_str, humidity, temperature_c
            )
        )

        temperature_c = dhtDevice2.temperature
        temperature_f = temperature_c * (9 / 5) + 32
        humidity = dhtDevice2.humidity
        now = datetime.datetime.now()
        date_time_str = now.strftime("%d.%m.%Y;%H:%M")
        print(
            "H;{};{:.1f};{}".format(
                date_time_str, humidity, temperature_c
            )
        )
- Raspberry Pi 400
- Debian GNU/Linux 11 (bullseye)
- Python 3.9.2
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du nur eine Zeile willst, dann eben nur ein print. Und wenn du in der einen Zeile mehr Informationen willst, dann muessen alle Informationen in die eine Zeile. Wenn man durch copy & paste den gleichen Code die gleichen Variablen anpassen laesst, so dass der zweite Sensor die Werte des ersten ueberschreibt, dann muss man die Variablennamen aendern. So wie man das mit dhtDevice2 auch schon gemacht hat. Wenn auch besser dhtHeizung und dhtRaum benutzt werden sollten.
Bierschors
User
Beiträge: 3
Registriert: Sonntag 16. Oktober 2022, 11:20

Ich habe es jetzt so gemacht:

Anstatt

Code: Alles auswählen

print(
habe ich jetzt

Code: Alles auswählen

sys.stdout.write(
eingegeben, vorher natürlich noch

Code: Alles auswählen

import sys
Jetzt stehts nebeneinander.
- Raspberry Pi 400
- Debian GNU/Linux 11 (bullseye)
- Python 3.9.2
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Naja, wenn Du keine neue Zeile möchtest, dann kannst Du auch bei print `end=""` angeben.
Aber wie __deets__ schon geschrieben hat, warum packst Du nicht einfach alle Ausgaben in ein print?
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bierschors: Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Der Kommentar beim initialisieren ist offensichtlich, und der Kommentar ``# Ausgabe`` ist im Grund falsch, weil sehr unvollständig, denn dort passiert *alles* — Messung, Aufbereitung/Berechnung, und Ausgabe — nicht nur die Ausgabe.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Man nummeriert keine Namen. Dann will man sich entweder bessere Namen überlegen, oder gar keine Einzelnamen/-werte verwenden, sondern eine Datenstruktur. Oft eine Liste. Hier auch. Dann wird man auch den doppelten Code los, weil man eine Schleife über die Sensoren und den Ort des jeweiligen Sensors schreiben kann.

Die Temperatur in Fahrenheit wird berechnet, aber nirgends verwendet.

Datum und Zeit sollten keine getrennten Spalten sein. Der Messzeitpunkt ist *ein* Wert, der zusammengehört. Ich würde da auch empfehlen ein international standardisiertes Format zu wählen. Und man sollte sich überlegen ob man das nicht besser als UTC-Zeit speichert, statt als lokale Zeit, denn bei lokaler Zeit passieren mindestens zweimal im Jahr komische Dinge, weil die Zeit eine Stunde vor/zurück ”springt”, was man dann bei der Auswertung besonders behandeln muss, und insbesondere bei doppelten Zeiten ist das nervig.

Statt `format()` könnte man hier ein f-Zeichenkettenliteral verwenden. Wobei man sich überlegen sollte ob man CSV-Daten nicht besser mit dem `csv`-Modul erstellt.

`RuntimeError` ist supergenerisch und davon einfach einen nur so kleinen Teil mit `print()` auszugeben ist keine gute Idee. Dass das auf dem gleichen Ausgabekanal wie die Daten landet, aber ein anderes Format hat, ist auch ungünstig. So etwas würde man dann eher nach `stderr` schreiben, und um die Fehlersuche zu vereinfachen, oder gar erst zu ermöglichen, würde man da deutlich mehr Informationen ausgeben wollen. Was zum Beispiel `logging` oder das externe `loguru` mit der entsprechenden `exception()`-Methode machen.

Das `time.sleep()` und ``continue`` ist überflüssig, weil genau das gleiche passiert, wenn man das weg lässt.

Jede `Exception` behandeln in dem man eine Ressource aufräumt und dann die Ausnahme 1:1 wieder auslöst ist die Aufgabe von ``finally`` und nicht von ``except`` und das ``try``/``finally`` gehört dann auch an eine Stelle wo es verständlicher ist.

Dann wird dort nicht alles aufgeräumt, weil nur auf *einem* `dhtDevice` die `exit()`-Methode aufgerufen wird. Du hast aber mittlerweile zwei davon.

Zwischenstand der immer noch das macht, was das Ursprungsprogramm tut:

Code: Alles auswählen

#!/usr/bin/env python3
import logging
import time
from datetime import datetime as DateTime

import adafruit_dht
import board

SENSOR_PINS = [board.D4, board.D17]


def main():
    dht_devices = [adafruit_dht.DHT22(pin) for pin in SENSOR_PINS]
    try:
        while True:
            try:
                for place, dht_device in zip(dht_devices, ["Heizung", "Raum"]):
                    print(
                        f"{place};{DateTime.now():%Y-%m-%d %H:%M:%S};"
                        f"{dht_device.humidity:.1f};{dht_device.temperature}"
                    )

            except RuntimeError:
                #
                # Bei Auslesefehler einfach weiterlesen.
                #
                logging.exception("Problem beim Schleifendurchlauf.")

            time.sleep(5 * 60)

    finally:
        for dht_device in dht_devices:
            try:
                dht_device.exit()
            except:
                logging.exception("Fehler beim verlassen.")


if __name__ == "__main__":
    main()
Und ich würde dazu raten das Format so zu lassen, denn das ist leichter auswertbar und nicht schwerer wie Du anscheinend vermutest. Und es ist auch robuster gegen Änderungen, wenn da Sensoren hinzu kommen oder weg fallen. Der Ort ist ein eigenständiger Wert in einer Messung, und sollte nicht im Spaltenindex oder -namen stecken.

Falls man beim erstellen der `DHT22`-Objekte mit Ausnahmen rechnen muss, ist das so immer noch nicht robust, und man braucht einen `contextlib.ExitStack` mit dem man das Aufräumen absichern kann.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Bierschors
User
Beiträge: 3
Registriert: Sonntag 16. Oktober 2022, 11:20

@__blackjack__ Ja, werde es mir zu Herzen nehmen.

@all

Bin jetzt umgeschwenkt von DHT22 auf DS18B20. Ist nicht so anfällig und funktioniert auch draußen.

Auslesen und in Datei schreiben, das klappt alles soweit, so wie ich es haben will.
NUR, jedesmal (hier alle 5 Sekunden) bzw. normal alle 180 Sekunden, werden die Werte ausgelesen aber es werden immer 3 Zeilen geschrieben.
"print" habe ich nur jetzt hier reingebastelt um schneller zu kontrollieren. Ist normalerweise nicht drin aber so spare ich mir jedesmal die Datei neu zu öffnen.
Das mit der LED ist nur eine Spielerei, soll nur darstellen das er alle 180 Sekunden misst.

Hier mal ein Beispiel:

Im lxterminal siehts so aus:

pi@pi:~ $ python3 DS18B20.py
23.10.2022;20:44:26;
Draußen: 14.0 °C
Heizung: 0.0 °C
Wohnzimmer: 0.0 °C

23.10.2022;20:44:26;
Draußen: 14.0 °C
Heizung: 19.3 °C
Wohnzimmer: 0.0 °C

23.10.2022;20:44:26;
Draußen: 14.0 °C
Heizung: 19.3 °C
Wohnzimmer: 20.9 °C

23.10.2022;20:44:34;
Draußen: 14.0 °C
Heizung: 19.3 °C
Wohnzimmer: 20.9 °C

23.10.2022;20:44:34;
Draußen: 14.0 °C
Heizung: 19.4 °C
Wohnzimmer: 20.9 °C

23.10.2022;20:44:34;
Draußen: 14.0 °C
Heizung: 19.4 °C
Wohnzimmer: 20.9 °C


in der Datei siehts so aus:

23.10.2022;20:44:26;14.0;Draußen;0.0;Heizung;0.0;Wohnzimmer
23.10.2022;20:44:26;14.0;Draußen;19.3;Heizung;0.0;Wohnzimmer
23.10.2022;20:44:26;14.0;Draußen;19.3;Heizung;20.9;Wohnzimmer
23.10.2022;20:44:34;14.0;Draußen;19.3;Heizung;20.9;Wohnzimmer
23.10.2022;20:44:34;14.0;Draußen;19.4;Heizung;20.9;Wohnzimmer
23.10.2022;20:44:34;14.0;Draußen;19.4;Heizung;20.9;Wohnzimmer


Ich komme ums verrecken nicht drauf, warum. :roll:
Wenn ein Wert auf 0 ist würde ich es verstehen, das er nochmal nachliest. Problem wäre aber, wenn es Draußen genau 0° hätte.

(Nur zum Verständnis, ich lerne noch und bin ganz am Anfang, hatte bis vor 2 Woche noch gar keine Ahnung da es überhaupt Python gibt. Also wenn der Code hier nicht ganz sauber ist. Ich probiere erst noch rum. Aber hier hänge ich jetzt wirklich)

Hier der Code:

Code: Alles auswählen

#!/usr/bin/python3
#coding=utf8
#DS18B20_.py

from gpiozero import LED
import datetime
import time
import locale

locale.setlocale(locale.LC_ALL, ('de_DE.utf8'))

led = LED(12)
led.blink(1,180)

base_dir = '/sys/bus/w1/devices'

device_files = []
device_files.append(base_dir + '/28-3c01f0966a28/w1_slave')
device_files.append(base_dir + '/28-3c01f0963797/w1_slave')
device_files.append(base_dir + '/28-3c3af6491a6e/w1_slave')

def read_temp_raw(device_file):
	f = open(device_file, 'r')
	lines = f.readlines()
	f.close()
	return lines

def read_temp(device_file):
	lines = read_temp_raw(device_file)
	while lines[0].strip()[-3:] !='YES':
		time.sleep(0.2)
		lines = read_temp_raw_a
	equals_pos = lines[1].find('t=')
	if equals_pos != -1:
		temp_string = lines[1][equals_pos+2:]
		temp_c = int(temp_string) / 1000
		temp_c = str(round(temp_c,1))
		return temp_c

temperature_results = [0.0]*len(device_files)

while True:

	now = datetime.datetime.now()
	for device_num, device_file in enumerate(device_files):
		temperature_results[device_num]=read_temp(device_file)
		
		with open('Output.txt', 'a') as text_file:
			text_file.write(str(now.strftime('%d.%m.%Y;%H:%M:%S;')+''))
			text_file.write(str(temperature_results[0])+'')
			text_file.write(str(';Draußen;')+'')
			text_file.write(str(temperature_results[1])+'')
			text_file.write(str(';Heizung;')+'')
			text_file.write(str(temperature_results[2])+'')
			text_file.write(str(';Wohnzimmer')+'\n')
		text_file.close()
		
		print(now.strftime('%d.%m.%Y;%H:%M:%S;'))
		print("Draußen: " + str(temperature_results[0]) + " °C"'')
		print("Heizung: " + str(temperature_results[1]) + " °C"'')
		print("Wohnzimmer: " + str(temperature_results[2]) + " °C"'\n')

	time.sleep(5)
- Raspberry Pi 400
- Debian GNU/Linux 11 (bullseye)
- Python 3.9.2
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Eingerückt wird immer mit vier Leerzeichen pro Ebene, nicht mit Tabs.
Dateien öffnet man mit dem with-Statement, auch in read_temp_raw. Obwohl, die Funktion ist überflüssig, weil das Dateiöffnen man genausogut in read_temp machen könnte. Das ist dann ziemlich falsch. read_temp_raw_a gibt es nicht. Mit Indizes und find fummelt man nicht rum, wenn man es nicht vermeiden kann. Die Funktion liefert implizit None zurück, was sie eine saubere Funktion nicht tun sollte.
Gerundet wird bei der Ausgabe und bei einer Funktion, die eine Temperatur liest, würde ich eine Zahl als Rückgabewert erwarten.
Das ganze könnte so aussehen:

Code: Alles auswählen

def read_temperature(device_file):
    with open(device_file) as lines:
        if not next(lines).strip.endswith('YES'):
            # on error return None
            return None
        try:
            _, _, temperature = next(lines).partition('t=')
            return int(temperature) / 1000
        except ValueError:
            return None
Strings stückelt man nicht mit + zusammen. Es macht vor allem keinen Sinn, leere Strings explizit anzuhängen. Bei with muß man nicht explizit close aufrufen. Wenn das eine csv-Datei sein soll, dann benutzt man das csv-Modul.
Wenn Du nicht möchtest, dass immer drei Zeilen geschrieben werden, dann solltest Du das einfach nicht innerhalb der for-Schleife tun.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

da ich auch schon am schreiben war, Sirius3 aber schneller war, wollte ich dir wenigstens noch den Hinweis mit geben, dass du dein Problem an dem Inhalt der Textdatei erkennen kannst.
In jeder Zeile kommt ein Temperaturwert mehr dazu, das bedeutet, dass wohl nach jedem Sensorauslesen eine Zeile geschrieben wurde.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
DeaD_EyE
User
Beiträge: 1017
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ich wollte eigentlich wegen des Pull-up-Widerstands nachsehen. Ich meine, das waren 4.7 KOhm, kann mich aber auch irren.
Jedenfalls steht beim DHT22 etwas ganz Eigenartiges:
1-wire bus is used for communication between MCU and AM2302. (Our 1-wire bus is specially designed, it's
different from Maxim/Dallas 1-wire bus, so it's incompatible with Dallas 1-wire bus.
)
Quelle. https://cdn-shop.adafruit.com/datasheet ... AM2302.pdf

Da frage ich mich, wieso der DHT22 überhaupt irgendwo funktioniert hat oder kommt der Linux-Kernel mit dem eigenen Protokoll auch zurecht?

Falls du einen ganz anderen Sensor hast, dann mal bitte das Datenblatt dazu posten.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten