Auslesen von DHT22 an GPIO von Raspberry funktioniert nicht mehr

Begonnen von Holger86, 14 Februar 2024, 12:13:40

Vorheriges Thema - Nächstes Thema

Holger86

Es wurde bereits viel zu dem Thema geschrieben und ich habe auch einiges davon probiert, aber leider komme ich nicht weiter und hoffe hier auf Hilfe. Die SD-Karte meines Raspberry hat sich verabschiedet und ich habe die Gelegenheit genutzt, mein FHEM-System neu aufzusetzen. Also Raspberry Pi OS Lite bookworm 64 bit und alles rund um FHEM installiert und grundsätzlich läuft alle wunderbar bis auf meinen direkt am Raspberry angeschlossenen Sensor DHT22.

Meine alte Lösung lief mit wiringpi und loldht. Beides wird nicht weiterentwickelt und läuft unter bookworm nicht mehr.

Bei RPI_1Wire ist das Auslesen des DHT22 angeblich über das Perl-Modul RPi::DHT möglich. Das benötigt jedoch auch wieder wiringpi, was nicht mehr läuft und wohl auch nicht mehr weiterentwickelt wird.

Adafruit_DHT lässt sich installieren, liefert aber keine Messwerte, sondern nur "unknown system". Meine Python-Kenntnisse sind nun aber auch nicht so gut, dass ich das anpassen könnte.

Ein weiterer Versuch mit adafruit-circuitpython-dht scheitert schon beim Installationsversuch.

Ich hoffen nun, jemand hier im Forum hat einen lokal angebundenen DHT22 überreden können, unter bookworm Messwerte zu liefern. Wenn ja, freue ich mich (und sicher auch andere Hilfesuchende) hier über eine funktionierende Anleitung.

Adimarantis

Ich hatte mich schon mal dran versucht statt wiringPi libgpiod zu verwenden.
Das funktioniert auf meinem Raspi400 mit 64-Bit Bookworm auf jeden Fall - zumindest bin ich soweit gekommen damit eine LED ein- und auszuschalten.
Ob damit dann das bit-banging für den DHT11/22 funktioniert, hab ich dann nicht weiter vertieft.

Damit könnte man versuchen mein RPi::DHT umzuschreiben - aber das erfordert Zeit und Motivation. Mal sehen.
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Adimarantis

Jetzt bin ich auf eine bessere Lösung gestossen:
Der Linux Kernel hat tatsächlich DHT11/22 support bereits eingebaut und es gibt Overlay Module dafür.
Dazu in der /boot/config.txt folgende Zeile einfügen:
dtoverlay=dht11,gpiopin=6
und den Raspberry neu starten.
Danach sollte es ein sysfs Verzeichns
/sys/devices/platform/dht11@6/iio:device0geben. Die Dateien  in_temp_input und in_humidityrelative_input enthalten Temperatur und Feuchtigkeit (mal 1000).
Zumindest bei meinem DHT11 funktioniert das - es gibt zwar häufig einen Input/Output Error, aber wenn man es mehrmalig versucht kommen Werte.
Wenn die ich Doku richtig verstanden habe, sollte das auch für den DHT22 funktionieren (ein extra DHT22 Modul gibt es nicht).

Probier das mal aus.
Mein RPI_1Wire Modul so zu erweitern, dass es diese Schnittstelle ausliest (z.B. wenn RPi::DHT nicht installiert ist), sollte maximal mal ein Abend Arbeit sein.
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Adimarantis

Wird wohl ein Monolog hier  :)
Hab das Ganze mal implementiert. Vielleicht mag es ja noch jemand testen bevor ich das einchecke.

Wichtig ist das RPi::DHT nicht installiert ist (sonst wird versucht es zu verwenden, selbst wenn es unter Bookworm nicht funktioniert) - also im Zweifelsfall löschen (/usr/local/lib/aarch64-linux-gnu/perl/5.36.0/RPi).
Außerdem muss in /boot/config.txt das dtoverlay=dht11,gpiopin=6 eingetragen sein.

Hab das sowohl mit DHT22 und DHT11 probiert. Der DHT22 funktioniert ganz gut, aber ich habe neuere DHT11, welche bei der Temperatur eine Dezimalstelle liefern. Das versteht der Kerneloverlay nicht (dmesg: dht11 dht11@6: Don't know how to decode data: 50 0 24 3), daher bekomme ich für die DHT11 nur Werte, wenn die Dezimalstelle 0 ist - also sehr selten.
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Adimarantis

Und es gibt für die dht11 Sensoren noch eine Lösung:
https://github.com/dead-beef/dht11-decimal

Das Projekt installiert eine veränderte Overlay Datei, die leider aber dann anders heisst.
Daher nochmal eine neue Version die beide Varianten probiert.
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Holger86

#5
Als erstes mal danke für die bisherigen Beiträge. Es wird natürlich kein Monolog.

Inzwischen habe ich mein Problem gelöst bekommen mit Hilfe eines Linux-Admins aus meiner Firma. Der hat mich darauf gebracht, dass es ab bookworm für den Raspberry Pi einen eigenen Demon für die GPIO-Pins gibt. Da muss dann auch nichts an der config.txt gedreht werden. Der Demon heißt pigpiod. Dieser Demon sollte als erstes für automatischen Start eingerichtet werden:
sudo systemctl enable pigpiodAnschließend kann man gut mit den GPIO-Pins arbeiten, bevorzugt mit Python 3, was auch bereits bei bookworm dabei ist.

In Anlehnung an http://abyz.me.uk/rpi/pigpio/ habe ich mir daher ein Python-Skript DHT22.py (s. Dateianhang) geschrieben, das den DHT22 ausliest. Dort ist auch der genutzte GPIO-Pin, an dem der Sensor angeschlossen ist, abgelegt. Das habe ich im Verzeichnis /opt/fhem/dht22/ abgelegt und für mich passend berechtigt:
chown fhem:dialout dht22 -R
chmod 775 dht22 -R
Wichtig! Der Sensor wird im Skript zweimal hintereinander ausgelesen, da sich gezeigt hat, dass die erste Auslesung sehr ungenaue Werte ergibt.

Dieses Skript wird nun, wie früher loldht, mittels einer Funktion der Datei 99_myUtils.pm angesprochen:
# DHT 22
# Messwerte vom Sensor DHT 22 einlesen und bearbeiten
# Der GPIO-Pin, an dem der Sensor angeschlossen ist.
# wird im Skript /opt/fhem/dht22/DHT22.py angegeben.
sub DHT22($) {
   my ($Dummy) = @_;

   # Aufruf des Python-Skripts und Umleitung der auftretenden Fehlermeldung nach /dev/null
   my $cmd = 'python /opt/fhem/dht22/DHT22.py 2>/dev/null';

   # Variablen deklarieren
   my @linesArray;
   my $line;
   my @lineElements;
   my $Humidity = '0';
   my $Temperature = '0';

   open DATA, "$cmd |" or die "Couldn't execute program: $!";
        @linesArray = <DATA>;
   close DATA;

   foreach $line (@linesArray) {
       # Zeile an Leerzeichen aufteilen
           @lineElements = split(/ /,$line);
           if ($lineElements[0] eq 'Humidity') {
               $Humidity = ($lineElements[2] + 1.3) * 1;     # Offset und Anstieg zur Kalibrierung
               $Temperature = ($lineElements[6] - 2.4) * 1;  # Offset und Anstieg zur Kalibrierung
           }
   }
   # Setzt das Reading des Dummy $Dummy für die Temperatur
   fhem("setreading $Dummy temperature $Temperature");
   # Setzt das Reading des Dummy $Dummy für die Luftfeuchte
   fhem("setreading $Dummy humidity $Humidity");
}
Hier kann der Sensor auch noch linear kalibriert werden. Bei meinem Sensor ist die angezeigte Temperatur 1,3 °C zu niedrig und die Luftfeuchtigkeit 2,4 % zu hoch gegenüber meiner Referenz.

Die Abfrage bei mir erfolgt dann im FHEM zeitgesteuert mit defmod sz_DHT22_abfragen at +*00:05 {DHT22("sz_DHT22")} in den Dummy defmod sz_DHT22 dummy
attr sz_DHT22 alias Temperatur/Feuchte-Sensor
attr sz_DHT22 devStateStyle style="text-align: left"
attr sz_DHT22 group Raumklima
attr sz_DHT22 icon temperature_humidity
attr sz_DHT22 room Arbeitszimmer
attr sz_DHT22 stateFormat Temperatur: temperature °C <br> \
Relative Luftfeuchte: humidity % <br> \
Absolute Luftfeuchte: absHumidity g/m³

Zum Schluss noch das recht lange Python-Skript DHT22.py:
#!/usr/bin/env python

# 2024-02-15 Holger Linde   - Modified for use in FHEM
#                           - Read DHT22 two times and use only the last result
#                           - DHT22 DATA connected on GPIO 26

import time
import atexit
import pigpio

GPIO_Pin = 26

class sensor:
   """
   A class to read relative humidity and temperature from the
   DHT22 sensor.  The sensor is also known as the AM2302.
   """

   def __init__(self, pi, gpio, LED=None, power=None):
      """
      Instantiate with the Pi and gpio to which the DHT22 output
      pin is connected.

      Optionally a LED may be specified.  This will be blinked for
      each successful reading.

      Optionally a gpio used to power the sensor may be specified.
      This gpio will be set high to power the sensor.  If the sensor
      locks it will be power cycled to restart the readings.

      Taking readings more often than about once every two seconds will
      eventually cause the DHT22 to hang.  A 3 second interval seems OK.
      """

      self.pi = pi
      self.gpio = gpio
      self.LED = LED
      self.power = power

      if power is not None:
         pi.write(power, 1) # Switch sensor on.
         time.sleep(2)

      self.powered = True

      self.cb = None

      atexit.register(self.cancel)

      self.bad_CS = 0 # Bad checksum count.
      self.bad_SM = 0 # Short message count.
      self.bad_MM = 0 # Missing message count.
      self.bad_SR = 0 # Sensor reset count.

      # Power cycle if timeout > MAX_TIMEOUTS.
      self.no_response = 0
      self.MAX_NO_RESPONSE = 2

      self.rhum = -999
      self.temp = -999

      self.tov = None

      self.high_tick = 0
      self.bit = 40

      pi.set_pull_up_down(gpio, pigpio.PUD_OFF)

      pi.set_watchdog(gpio, 0) # Kill any watchdogs.

      self.cb = pi.callback(gpio, pigpio.EITHER_EDGE, self._cb)

   def _cb(self, gpio, level, tick):
      """
      Accumulate the 40 data bits.  Format into 5 bytes, humidity high,
      humidity low, temperature high, temperature low, checksum.
      """
      diff = pigpio.tickDiff(self.high_tick, tick)

      if level == 0:

         # Edge length determines if bit is 1 or 0.

         if diff >= 50:
            val = 1
            if diff >= 200: # Bad bit?
               self.CS = 256 # Force bad checksum.
         else:
            val = 0

         if self.bit >= 40: # Message complete.
            self.bit = 40

         elif self.bit >= 32: # In checksum byte.
            self.CS  = (self.CS<<1)  + val

            if self.bit == 39:

               # 40th bit received.

               self.pi.set_watchdog(self.gpio, 0)

               self.no_response = 0

               total = self.hH + self.hL + self.tH + self.tL

               if (total & 255) == self.CS: # Is checksum ok?

                  self.rhum = ((self.hH<<8) + self.hL) * 0.1

                  if self.tH & 128: # Negative temperature.
                     mult = -0.1
                     self.tH = self.tH & 127
                  else:
                     mult = 0.1

                  self.temp = ((self.tH<<8) + self.tL) * mult

                  self.tov = time.time()

                  if self.LED is not None:
                     self.pi.write(self.LED, 0)

               else:

                  self.bad_CS += 1

         elif self.bit >=24: # in temp low byte
            self.tL = (self.tL<<1) + val

         elif self.bit >=16: # in temp high byte
            self.tH = (self.tH<<1) + val

         elif self.bit >= 8: # in humidity low byte
            self.hL = (self.hL<<1) + val

         elif self.bit >= 0: # in humidity high byte
            self.hH = (self.hH<<1) + val

         else:               # header bits
            pass

         self.bit += 1

      elif level == 1:
         self.high_tick = tick
         if diff > 250000:
            self.bit = -2
            self.hH = 0
            self.hL = 0
            self.tH = 0
            self.tL = 0
            self.CS = 0

      else: # level == pigpio.TIMEOUT:
         self.pi.set_watchdog(self.gpio, 0)
         if self.bit < 8:       # Too few data bits received.
            self.bad_MM += 1    # Bump missing message count.
            self.no_response += 1
            if self.no_response > self.MAX_NO_RESPONSE:
               self.no_response = 0
               self.bad_SR += 1 # Bump sensor reset count.
               if self.power is not None:
                  self.powered = False
                  self.pi.write(self.power, 0)
                  time.sleep(2)
                  self.pi.write(self.power, 1)
                  time.sleep(2)
                  self.powered = True
         elif self.bit < 39:    # Short message receieved.
            self.bad_SM += 1    # Bump short message count.
            self.no_response = 0

         else:                  # Full message received.
            self.no_response = 0

   def temperature(self):
      """Return current temperature."""
      return self.temp

   def humidity(self):
      """Return current relative humidity."""
      return self.rhum

   def staleness(self):
      """Return time since measurement made."""
      if self.tov is not None:
         return time.time() - self.tov
      else:
         return -999

   def bad_checksum(self):
      """Return count of messages received with bad checksums."""
      return self.bad_CS

   def short_message(self):
      """Return count of short messages."""
      return self.bad_SM

   def missing_message(self):
      """Return count of missing messages."""
      return self.bad_MM

   def sensor_resets(self):
      """Return count of power cycles because of sensor hangs."""
      return self.bad_SR

   def trigger(self):
      """Trigger a new relative humidity and temperature reading."""
      if self.powered:
         if self.LED is not None:
            self.pi.write(self.LED, 1)

         self.pi.write(self.gpio, pigpio.LOW)
         time.sleep(0.017) # 17 ms
         self.pi.set_mode(self.gpio, pigpio.INPUT)
         self.pi.set_watchdog(self.gpio, 200)

   def cancel(self):
      """Cancel the DHT22 sensor."""

      self.pi.set_watchdog(self.gpio, 0)

      if self.cb != None:
         self.cb.cancel()
         self.cb = None

if __name__ == "__main__":

   import time
   import pigpio
   import DHT22

   # Intervals of about 2 seconds or less will eventually hang the DHT22.
   INTERVAL = 3
   pi = pigpio.pi()
   s = DHT22.sensor(pi, GPIO_Pin)
   r = 0

   next_reading = time.time()

   while r < 2:

      r += 1
      s.trigger()
      time.sleep(0.2)
      next_reading += INTERVAL

      if r == 1:
         time.sleep(next_reading-time.time()) # Overall INTERVAL second polling.

   print('Humidity = {0:0.1f} % Temperature = {1:0.1f} *C'.format(s.humidity(), s.temperature()))

   s.cancel()
   pi.stop()

mumpitzstuff