.. include:: /index.rst
:start-after: start_hello_message
:end-before: end_hello_message
.. _py_room_monitor:
4.12 Umweltmonitor
==============================================
**Einführung**
In dieser Lektion bauen Sie ein **Umweltmonitor-Dashboard**, das Temperatur, Luftfeuchtigkeit und Umgebungslicht misst und alle Werte in Echtzeit auf einem 128×64-OLED-Bildschirm anzeigt.
Dieses Projekt verwendet:
- **DHT11-Sensor** zur Messung von Temperatur und Luftfeuchtigkeit
- **LDR (Light Dependent Resistor / Fotowiderstand)**, der über den ADC des Fusion HAT+ angeschlossen ist, zur Messung der Lichtstärke
- **SSD1306-OLED**, um Umweltdaten sowie einen dynamischen Lichtbalken anzuzeigen
Der Bildschirm wird kontinuierlich aktualisiert und zeigt einen Statusindikator (``OK`` oder ``TIMEOUT``), je nachdem, ob der DHT11 eine gültige Messung liefert.
----------------------------------------------
**Was Sie benötigen**
Für dieses Projekt werden die folgenden Komponenten benötigt:
.. list-table::
:widths: 30 20
:header-rows: 1
* - KOMPONENTENBESCHREIBUNG
- KAUFLINK
* - :ref:`cpn_wires`
- |link_wires_buy|
* - :ref:`cpn_humiture_sensor`
- |link_humiture_buy|
* - :ref:`cpn_photoresistor`
- |link_photoresistor_buy|
* - :ref:`cpn_oled`
- \-
* - :ref:`cpn_fusion_hat`
- \-
* - Raspberry Pi
- \-
.. ----------------------------------------------
.. **Schaltplan**
.. .. image:: img/fzz/4.12_room_monitor_sch.png
.. :width: 80%
.. :align: center
----------------------------------------------
**Verdrahtungsdiagramm**
Verwenden Sie das folgende Verdrahtungsdiagramm, um die Komponenten korrekt zu verbinden:
.. image:: img/fzz/4.12_room_monitor_bb.png
:width: 100%
:align: center
----------------------------------------------
**Einrichtungsschritte**
#. Installieren Sie die erforderlichen Bibliotheken:
.. raw:: html
.. code-block:: shell
sudo pip3 install adafruit-circuitpython-ssd1306 --break
#. Führen Sie den Beispielcode aus dem Verzeichnis ``ai-lab-kit`` aus:
.. raw:: html
.. code-block:: shell
cd ~/ai-lab-kit/python/
sudo python3 4.12_RoomMonitor.py
#. Wenn das Skript ausgeführt wird:
* Das OLED zeigt Temperatur, Luftfeuchtigkeit und den Lichtanteil in Prozent an
* Ein horizontaler Balken stellt die aktuelle Lichtstärke grafisch dar
* „OK“ oder „TIMEOUT“ zeigt den Lesestatus des DHT11-Sensors an
* Die Daten werden alle 0,5 Sekunden aktualisiert
* Drücken Sie **Ctrl+C**, um das Programm zu beenden und das Display zu löschen
----------------------------------------------
**Code**
Hier ist das Python-Skript für das Umweltmonitor-Dashboard:
.. raw:: html
.. code-block:: python
import time
from statistics import mean
from fusion_hat.modules import DHT11
from fusion_hat.adc import ADC
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
import board
# ---------- Hardware configuration ----------
DHT_PIN = 17 # BCM numbering for the DHT11 data pin
LDR_CH = 0 # ADC channel for LDR (e.g., 0,1, ...)
I2C_ADDR = 0x3C # OLED I2C address (commonly 0x3C)
# ---------- OLED setup ----------
WIDTH, HEIGHT = 128, 64
i2c = board.I2C()
oled = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=I2C_ADDR)
oled.fill(0)
oled.show()
# Framebuffer
image = Image.new("1", (WIDTH, HEIGHT))
draw = ImageDraw.Draw(image)
font = ImageFont.load_default()
def text_size(font, text):
l, t, r, b = font.getbbox(text)
return (r - l, b - t)
# ---------- Sensors ----------
dht = DHT11(pin=DHT_PIN)
ldr = ADC(LDR_CH)
# ---------- Light normalization ----------
# Map ADC raw (0..4095) to percentage (0..100). You can adjust the calibration
# range to your circuit by putting typical min/max readings below:
LDR_RAW_MIN = 0 # raw value in darkness (tune if needed)
LDR_RAW_MAX = 4095 # raw value in bright light (tune if needed)
def clamp(v, vmin, vmax):
return vmax if v > vmax else vmin if v < vmin else v
def linear_map(x, in_min, in_max, out_min, out_max):
if in_max == in_min:
return out_min
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
# Simple moving average for light to reduce flicker
_light_window = []
def light_percent(raw):
"""Convert raw ADC to smoothed light percentage."""
global _light_window
# Windowed average of last few samples
_light_window.append(raw)
if len(_light_window) > 5:
_light_window.pop(0)
smooth_raw = int(mean(_light_window))
pct = linear_map(smooth_raw, LDR_RAW_MIN, LDR_RAW_MAX, 0, 100)
return int(clamp(pct, 0, 100)), smooth_raw
# ---------- UI drawing ----------
BAR_W, BAR_H = WIDTH - 16, 10 # width/height for the light bar
BAR_X, BAR_Y = 8, HEIGHT - 12 # position of the light bar
def draw_bar(x, y, w, h, percent):
"""Draw a horizontal bar [0..100]%."""
# Border
draw.rectangle((x, y, x + w, y + h), outline=255, fill=0)
# Fill
fill_w = int((w - 2) * percent / 100.0)
if fill_w > 0:
draw.rectangle((x + 1, y + 1, x + 1 + fill_w, y + h - 1), outline=0, fill=255)
def render_screen(temp_c, hum_pct, light_pct, raw_adc, status_text="OK"):
"""Render all text and graphics to the framebuffer."""
draw.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
# Title
title = "Env Monitor"
tw, th = text_size(font, title)
draw.text(((WIDTH - tw) // 2, 0), title, font=font, fill=255)
# Temperature & Humidity lines
line1 = f"Temp: {temp_c:.1f} degC"
line2 = f"Hum : {hum_pct:.1f} %"
draw.text((2, 16), line1, font=font, fill=255)
draw.text((2, 28), line2, font=font, fill=255)
# Light line and bar
line3 = f"Light: {light_pct:3d}% (raw {raw_adc})"
draw.text((2, 40), line3, font=font, fill=255)
draw_bar(BAR_X, BAR_Y, BAR_W, BAR_H, light_pct)
# Status (e.g., "OK" or "TIMEOUT")
sw, sh = text_size(font, status_text)
draw.text((WIDTH - sw - 2, 0), status_text, font=font, fill=255)
# ---------- Main loop ----------
# Keep last good readings so display stays meaningful if a DHT read times out
last_temp = 0.0
last_hum = 0.0
try:
while True:
# Read DHT11 (may return None / timeout)
status = "OK"
result = dht.read()
if result:
hum, temp = result # order per your DHT11 wrapper: (humidity, temperature)
last_temp, last_hum = float(temp), float(hum)
else:
status = "TIMEOUT"
# Read LDR
raw = ldr.read()
light_pct, raw_smooth = light_percent(raw)
# Draw to OLED
render_screen(last_temp, last_hum, light_pct, raw_smooth, status_text=status)
oled.image(image)
oled.show()
# Console log (optional)
# print(f"T={last_temp:.1f}C H={last_hum:.1f}% Light={light_pct}% (raw {raw_smooth}) [{status}]")
time.sleep(0.5)
except KeyboardInterrupt:
oled.fill(0)
oled.show()
print("\nExited.")
----------------------------------------------
**Code verstehen**
1. **Importe**
Das Skript verwendet mehrere Module:
- ``DHT11`` zur Messung von Temperatur und Luftfeuchtigkeit
- ``ADC`` zum Auslesen der Helligkeit des LDR über den analogen Eingang
- ``PIL`` zum Zeichnen der Grafiken auf dem OLED
- ``adafruit_ssd1306`` zur Steuerung des OLED-Displays
- ``mean()`` zur Glättung der Lichtmesswerte
2. **OLED-Setup**
Ein 128×64-SSD1306-OLED wird über I2C initialisiert.
Ein Framebuffer (Pillow-Bild) wird verwendet, um alle UI-Elemente zu zeichnen, bevor sie auf das Display übertragen werden.
.. code-block:: python
# ---------- OLED setup ----------
WIDTH, HEIGHT = 128, 64
i2c = board.I2C()
oled = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=I2C_ADDR)
oled.fill(0)
oled.show()
# Framebuffer
image = Image.new("1", (WIDTH, HEIGHT))
draw = ImageDraw.Draw(image)
font = ImageFont.load_default()
3. **Sensorwerte lesen**
- Der DHT11 kann gelegentlich fehlschlagen. In diesem Fall zeigt das Skript ``TIMEOUT`` an, behält jedoch die zuletzt gültigen Werte bei.
- Der rohe LDR-Wert (0..4095) wird mithilfe eines gleitenden Durchschnitts geglättet, um Flackern zu reduzieren.
.. code-block:: python
# Read DHT11 (may return None / timeout)
status = "OK"
result = dht.read()
if result:
hum, temp = result # order per your DHT11 wrapper: (humidity, temperature)
last_temp, last_hum = float(temp), float(hum)
else:
status = "TIMEOUT"
# Read LDR
raw = ldr.read()
light_pct, raw_smooth = light_percent(raw)
4. **Lichtwert umrechnen**
``linear_map()`` wandelt den rohen ADC-Wert in einen Prozentwert (0..100 %) um.
``clamp()`` stellt sicher, dass der endgültige Wert im gültigen Bereich bleibt.
.. code-block:: python
def light_percent(raw):
"""Convert raw ADC to smoothed light percentage."""
global _light_window
# Windowed average of last few samples
_light_window.append(raw)
if len(_light_window) > 5:
_light_window.pop(0)
smooth_raw = int(mean(_light_window))
pct = linear_map(smooth_raw, LDR_RAW_MIN, LDR_RAW_MAX, 0, 100)
return int(clamp(pct, 0, 100)), smooth_raw
5. **Dashboard darstellen**
Das OLED zeigt folgende Informationen an:
- Titel
- Temperatur (°C)
- Luftfeuchtigkeit (%)
- Lichtanteil in Prozent sowie den rohen ADC-Wert
- Ein horizontales Balkendiagramm für die Lichtintensität
- Einen Statushinweis zum Zustand des Sensors
.. code-block:: python
def render_screen(temp_c, hum_pct, light_pct, raw_adc, status_text="OK"):
"""Render all text and graphics to the framebuffer."""
draw.rectangle((0, 0, WIDTH, HEIGHT), outline=0, fill=0)
# Title
title = "Env Monitor"
tw, th = text_size(font, title)
draw.text(((WIDTH - tw) // 2, 0), title, font=font, fill=255)
# Temperature & Humidity lines
line1 = f"Temp: {temp_c:.1f} degC"
line2 = f"Hum : {hum_pct:.1f} %"
draw.text((2, 16), line1, font=font, fill=255)
draw.text((2, 28), line2, font=font, fill=255)
# Light line and bar
line3 = f"Light: {light_pct:3d}% (raw {raw_adc})"
draw.text((2, 40), line3, font=font, fill=255)
draw_bar(BAR_X, BAR_Y, BAR_W, BAR_H, light_pct)
# Status (e.g., "OK" or "TIMEOUT")
sw, sh = text_size(font, status_text)
draw.text((WIDTH - sw - 2, 0), status_text, font=font, fill=255)
6. **Hauptschleife**
Alle 0,5 Sekunden:
- wird der DHT11 ausgelesen
- wird der LDR ausgelesen und geglättet
- wird der Bildschirm aktualisiert
- können optional Debug-Ausgaben ausgegeben werden
7. **Sauberes Herunterfahren**
Beim Drücken von ``Ctrl+C``:
- wird das OLED-Display gelöscht
- wird eine Meldung im Terminal ausgegeben
----------------------------------------------
**Fehlerbehebung**
- **DHT11 liefert häufig TIMEOUT**
- Erhöhen Sie die Verzögerung zwischen den Messungen.
- Überprüfen Sie die Verdrahtung und den Signaleingang.
- Stellen Sie sicher, dass bei Bedarf ein Pull-up-Widerstand verwendet wird.
- **OLED bleibt leer**
- Überprüfen Sie die I2C-Adresse (``0x3C``).
- Kontrollieren Sie die SDA/SCL-Verbindungen.
- Stellen Sie sicher, dass die erforderlichen Bibliotheken korrekt installiert sind.
- **Lichtwert scheint umgekehrt**
- Tauschen Sie ``LDR_RAW_MIN`` und ``LDR_RAW_MAX``.
- Überprüfen Sie die Beschaltung des LDR (Spannungsteiler).
- **Display flackert**
- Vergrößern Sie das Fenster für die Mittelwertbildung der Lichtmessung.
- Reduzieren Sie die Aktualisierungsrate (z. B. ``time.sleep(1.0)``).
----------------------------------------------
**Probieren Sie es selbst aus**
1. **Fahrenheit-Anzeige (°F) hinzufügen**
Zeigen Sie sowohl °C als auch °F auf dem Bildschirm an.
2. **Max/Min-Verlauf hinzufügen**
Speichern Sie die höchsten und niedrigsten Werte für Temperatur, Luftfeuchtigkeit oder Helligkeit.
3. **Warnsystem hinzufügen**
Lassen Sie den Rand des OLED blinken, wenn die Luftfeuchtigkeit zu niedrig oder die Temperatur zu hoch ist.
4. **Grafikmodus hinzufügen**
Zeichnen Sie verlaufende Diagramme für Temperatur- und Feuchtigkeitstrends.
5. **Animierte Symbole hinzufügen**
Verwenden Sie kleine Bitmaps für Sonne, Regen, Thermometer, Wassertropfen usw.
Diese Erweiterungen verwandeln den einfachen Umweltmonitor in ein umfangreiches Umwelt-Dashboard.