projekt:python_fastapi
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen RevisionVorhergehende ÜberarbeitungNächste Überarbeitung | Vorhergehende Überarbeitung | ||
| projekt:python_fastapi [2026/02/20 18:39] – torsten.roehl | projekt:python_fastapi [2026/02/23 07:26] (aktuell) – [Systemd] torsten.roehl | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | ====== | + | ====== Python FASTAPI |
| [[raspberry_pi: | [[raspberry_pi: | ||
| - | ====== GPIO Web – didaktisch sauber (FastAPI | + | //In diesem Projekt wird auf dem Raspberry Pi eine Weboberfläche mit FastAPI |
| - | Ziel: | + | |{{ :raspberry_pi:web_1.png? |
| - | * Klare Trennung: Hardware / Web / HTML | + | |Die LED-Ampel kann nun über den Webbrowser gesteuert und die Temperatur ausgelesen werden.| |
| - | * Keine Template-Engine | + | ====== Überblick ====== |
| - | * HTML als eigene Datei | + | * Voraussetzungen |
| - | * Einmalige Initialisierung | + | * Software |
| - | * Reboot-fest über systemd | + | * Konfiguration |
| - | * VENV: ~/ | + | |
| + | ====== Details ====== | ||
| - | ===== 1. PROJEKTORDNER | + | ===== Voraussetzungen |
| + | |||
| + | ==== ENV ==== | ||
| + | |||
| + | <note important> | ||
| + | **Aktivierung der Python-Environment: | ||
| + | |||
| + | Alle weiteren Schritte erfolgen mit der aktivierten Python-Umgebung. | ||
| + | |||
| + | < | ||
| + | source ~/ | ||
| + | </ | ||
| + | |||
| + | </ | ||
| + | Anschließend werden FastAPI und Uvicorn installiert: | ||
| <code bash> | <code bash> | ||
| - | cd / | + | pip install fastapi uvicorn |
| - | mkdir -p gpio_web/ | + | |
| - | mkdir -p gpio_web/ | + | |
| - | cd gpio_web/ | + | |
| - | touch __init__.py | + | |
| </ | </ | ||
| + | < | ||
| + | **FastAPI / Uvicorn** | ||
| + | * **FastAPI** | ||
| + | * stellt das Web-Framework bereit, mit dem die Webseiten und Routen programmiert werden. | ||
| + | * **Uvicorn** | ||
| + | * startet die Anwendung und sorgt dafür, dass sie im Browser erreichbar ist. | ||
| + | </ | ||
| - | ===== 2. VENV ===== | + | ==== Projektstruktur |
| + | '' | ||
| + | < | ||
| + | course_web/ | ||
| + | └── src/ | ||
| + | ├── app.py | ||
| + | ├── core/ | ||
| + | │ | ||
| + | │ | ||
| + | └── html/ | ||
| + | ├── led.html | ||
| + | └── temp.html | ||
| + | |||
| + | </ | ||
| + | ==== Apache2 Startseite ==== | ||
| + | Im Verzeichnis des Apache2-Webservers (''/ | ||
| + | Beim Aufruf der **IP-Adresse des Raspberry Pi im Browser** wird diese Startseite geladen, über die anschließend das gewünschte Projekt ausgewählt werden kann. | ||
| + | |||
| + | <note tip> **Tip** | ||
| + | |||
| + | Bevor die neue '' | ||
| <code bash> | <code bash> | ||
| - | source ~/devel/projects/course_env/bin/activate | + | sudo mv /var/www/html/index.html / |
| - | pip install fastapi uvicorn RPi.GPIO | + | |
| - | which uvicorn | + | |
| </ | </ | ||
| + | </ | ||
| - | Erwartet: | + | <code html /var/www/html/index.html> |
| - | | + | < |
| + | < | ||
| + | < | ||
| + | < | ||
| + | </head> | ||
| + | < | ||
| + | < | ||
| - | ===== 3. HARDWARE-SCHICHT ===== | + | < |
| + | < | ||
| + | < | ||
| + | < | ||
| + | </ul> | ||
| - | Datei: | + | </ |
| - | /home/pi/ | + | </html> |
| + | </code> | ||
| + | ==== Hardware ==== | ||
| + | Die Hardware, also die LED-Ampel und der Temperatursensor, | ||
| - | <code python> | + | ===== Software ===== |
| - | """ | + | Im folgenden Abschnitt werden die für die Webanwendung benötigten Python- und HTML-Dateien vorgestellt. Dazu gehören die Hardware-Anbindung über GPIO und den Temperatursensor, |
| - | Hardware-Schicht. | + | ==== API ==== |
| - | Kapselt alle GPIO-Zugriffe. | + | |
| - | """ | + | |
| + | <code python / | ||
| import RPi.GPIO as GPIO | import RPi.GPIO as GPIO | ||
| + | import glob | ||
| + | import time | ||
| + | |||
| + | # ----------------------------- | ||
| + | # API-Funktionen GPIO LED Ampel | ||
| + | # ----------------------------- | ||
| PIN_R = 17 | PIN_R = 17 | ||
| Zeile 56: | Zeile 110: | ||
| _initialized = False | _initialized = False | ||
| - | |||
| def init(): | def init(): | ||
| - | """ | ||
| - | Wird genau einmal beim Start des Servers aufgerufen. | ||
| - | """ | ||
| global _initialized | global _initialized | ||
| if _initialized: | if _initialized: | ||
| Zeile 76: | Zeile 126: | ||
| - | def setRedLED(value: int): | + | def setLED(pin, value): |
| - | GPIO.output(PIN_R, GPIO.HIGH if value == 1 else GPIO.LOW) | + | GPIO.output(pin, GPIO.HIGH if value == 1 else GPIO.LOW) |
| - | def setYellowLED(value: int): | + | def setRedLED(value): |
| - | | + | |
| - | def setGreenLED(value: | + | def setYellowLED(value): |
| - | | + | setLED(PIN_Y, |
| + | |||
| + | |||
| + | def setGreenLED(value): | ||
| + | | ||
| def status(): | def status(): | ||
| - | """ | ||
| - | Liefert aktuellen Hardware-Zustand. | ||
| - | """ | ||
| return ( | return ( | ||
| int(GPIO.input(PIN_R)), | int(GPIO.input(PIN_R)), | ||
| Zeile 97: | Zeile 148: | ||
| int(GPIO.input(PIN_G)), | int(GPIO.input(PIN_G)), | ||
| ) | ) | ||
| + | |||
| + | |||
| + | # ----------------------------- | ||
| + | # API-Funktionen ds18b20 | ||
| + | # ----------------------------- | ||
| + | |||
| + | SENSOR_TIMEOUT = 1 | ||
| + | |||
| + | def get_sensor(): | ||
| + | sensors = glob.glob("/ | ||
| + | if not sensors: | ||
| + | return None | ||
| + | return sensors[0] + "/ | ||
| + | |||
| + | |||
| + | def get_temperature(): | ||
| + | sensor_file = get_sensor() | ||
| + | if sensor_file is None: | ||
| + | return None | ||
| + | |||
| + | start_time = time.time() | ||
| + | |||
| + | while True: | ||
| + | with open(sensor_file, | ||
| + | lines = f.readlines() | ||
| + | |||
| + | if lines[0].strip().endswith(" | ||
| + | break | ||
| + | |||
| + | if time.time() - start_time > SENSOR_TIMEOUT: | ||
| + | return None | ||
| + | |||
| + | time.sleep(0.1) | ||
| + | |||
| + | temp_line = lines[1] | ||
| + | temp_str = temp_line.split(" | ||
| + | return float(temp_str) / 1000.0 | ||
| </ | </ | ||
| - | ===== 4. HTML (GETRENNT, KEIN TEMPLATE-FRAMEWORK) | + | ==== HTML ==== |
| + | |||
| + | === LED === | ||
| - | Datei: | ||
| - | / | ||
| - | <code html> | + | < |
| < | < | ||
| < | < | ||
| < | < | ||
| - | < | + | < |
| </ | </ | ||
| < | < | ||
| - | < | + | < |
| - | < | + | < |
| - | <a href="/web/ | + | <p>Status: {{R}}</p> |
| - | <a href="/web/ | + | <a href="/ |
| + | <a href="/ | ||
| - | < | + | < |
| - | <a href="/web/ | + | <p>Status: {{Y}}</p> |
| - | <a href="/web/ | + | <a href="/ |
| + | <a href="/ | ||
| - | < | + | < |
| - | <a href="/web/ | + | <p>Status: {{G}}</p> |
| - | <a href="/web/ | + | <a href="/ |
| + | <a href="/ | ||
| + | |||
| + | < | ||
| + | <a href="/"> | ||
| </ | </ | ||
| Zeile 132: | Zeile 226: | ||
| - | ===== 5. WEB-SCHICHT | + | === Temperature |
| + | |||
| + | |||
| + | <code html / | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | < | ||
| + | |||
| + | < | ||
| + | |||
| + | < | ||
| + | |||
| + | < | ||
| + | <a href="/"> | ||
| + | |||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== FASTAPI APP ==== | ||
| - | Datei: | + | <code python |
| - | | + | |
| - | <code python> | ||
| - | """ | ||
| - | Web-Schicht. | ||
| - | Verantwortlich für HTTP. | ||
| - | Keine GPIO-Logik hier. | ||
| - | """ | ||
| from fastapi import FastAPI, HTTPException | from fastapi import FastAPI, HTTPException | ||
| Zeile 148: | Zeile 259: | ||
| from core import hardware | from core import hardware | ||
| - | app = FastAPI(root_path="/ | + | app = FastAPI() |
| Zeile 156: | Zeile 267: | ||
| - | def load_html(r, y, g): | + | def load_template(name, replacements): |
| - | with open("static/ | + | |
| - | html = f.read() | + | |
| + | html = f.read() | ||
| + | except FileNotFoundError: | ||
| + | raise HTTPException(status_code=500, | ||
| - | | + | |
| - | html = html.replace(" | + | html = html.replace(key, str(value)) |
| - | html = html.replace(" | + | |
| return html | return html | ||
| - | + | @app.get("/ | |
| - | @app.get("/", | + | def led_page(): |
| - | def index(): | + | |
| r, y, g = hardware.status() | r, y, g = hardware.status() | ||
| - | return HTMLResponse(load_html(r, | ||
| + | return HTMLResponse( | ||
| + | load_template(" | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }) | ||
| + | ) | ||
| @app.get("/ | @app.get("/ | ||
| Zeile 177: | Zeile 298: | ||
| if value not in (0, 1): | if value not in (0, 1): | ||
| - | raise HTTPException(status_code=400, detail=" | + | raise HTTPException(status_code=400) |
| if color == " | if color == " | ||
| Zeile 186: | Zeile 307: | ||
| hardware.setGreenLED(value) | hardware.setGreenLED(value) | ||
| else: | else: | ||
| - | raise HTTPException(status_code=400, detail=" | + | raise HTTPException(status_code=400) |
| - | return RedirectResponse(url="/ | + | return RedirectResponse(url="/ |
| + | |||
| + | |||
| + | @app.get("/ | ||
| + | def temp_page(): | ||
| + | t = hardware.get_temperature() | ||
| + | |||
| + | if t is None: | ||
| + | value = " | ||
| + | else: | ||
| + | value = f" | ||
| + | |||
| + | return HTMLResponse( | ||
| + | load_template(" | ||
| + | " | ||
| + | }) | ||
| + | | ||
| </ | </ | ||
| - | ===== 6. APACHE | + | |
| + | |||
| + | ===== Konfiguration ===== | ||
| + | In diesem Abschnitt wird die Einbindung der **FastAPI-Anwendung** in den Apache-Webserver sowie die Einrichtung als '' | ||
| + | ==== Apache Proxy ==== | ||
| + | |||
| + | === Konfiguration === | ||
| + | |||
| + | In der Datei '' | ||
| + | innerhalb von ''< | ||
| + | |||
| + | |||
| + | <note tip> **Tip** | ||
| + | |||
| + | Bevor die Datei ''/ | ||
| + | sollte die vorhandene Konfiguration gesichert werden. | ||
| <code bash> | <code bash> | ||
| - | sudo a2enmod proxy | + | cd / |
| - | sudo a2enmod proxy_http | + | sudo cp 000-default.conf 000-default.conf.course_backup |
| - | sudo systemctl restart apache2 | + | |
| </ | </ | ||
| - | In: | + | </note> |
| - | | + | |
| - | Einfügen: | ||
| - | < | + | < |
| ProxyPreserveHost On | ProxyPreserveHost On | ||
| - | ProxyPass | + | |
| - | ProxyPassReverse /web | + | ProxyPass |
| - | RedirectMatch 301 ^/wiki$ /web | + | ProxyPassReverse /led |
| + | |||
| + | ProxyPass | ||
| + | ProxyPassReverse | ||
| </ | </ | ||
| + | |||
| + | === Aktivieren === | ||
| + | Damit die Weiterleitung an die FastAPI-Anwendung funktioniert, | ||
| <code bash> | <code bash> | ||
| - | sudo systemctl | + | sudo a2enmod proxy |
| + | sudo a2enmod proxy_http | ||
| + | sudo systemctl | ||
| </ | </ | ||
| + | ==== Systemd ==== | ||
| - | ===== 7. SYSTEMD ===== | + | Die Service-Datei muss unter ''/ |
| + | === Service | ||
| - | Datei: | ||
| - | / | ||
| - | < | + | < |
| [Unit] | [Unit] | ||
| - | Description=GPIO Web FastAPI | + | Description=Python |
| After=network-online.target | After=network-online.target | ||
| Wants=network-online.target | Wants=network-online.target | ||
| Zeile 230: | Zeile 387: | ||
| [Service] | [Service] | ||
| User=pi | User=pi | ||
| - | WorkingDirectory=/ | + | WorkingDirectory=/ |
| ExecStart=/ | ExecStart=/ | ||
| Restart=always | Restart=always | ||
| Zeile 237: | Zeile 394: | ||
| WantedBy=multi-user.target | WantedBy=multi-user.target | ||
| </ | </ | ||
| + | |||
| + | === Registrierung === | ||
| + | Damit die neu erstellte Service-Datei von '' | ||
| <code bash> | <code bash> | ||
| sudo systemctl daemon-reload | sudo systemctl daemon-reload | ||
| - | sudo systemctl enable | + | sudo systemctl enable |
| - | sudo systemctl start gpio_web | + | sudo systemctl start course_web |
| </ | </ | ||
| - | + | Nützlich: | |
| - | ===== 8. TEST ===== | + | |
| - | + | ||
| - | * http:// | + | |
| - | * http:// | + | |
| - | + | ||
| - | ===== 9. REBOOT ===== | + | |
| <code bash> | <code bash> | ||
| - | sudo reboot | + | sudo systemctl restart course_web |
| + | sudo systemctl status course_web | ||
| </ | </ | ||
| - | |||
| - | Nach Neustart erneut aufrufen. | ||
| - | |||
| - | Eigenschaften: | ||
| - | * HTML sauber getrennt | ||
| - | * Hardware sauber getrennt | ||
| - | * Keine versteckte Reinitialisierung | ||
| - | * Für Anfänger klar strukturiert | ||
| - | |||
projekt/python_fastapi.1771612741.txt.gz · Zuletzt geändert: von torsten.roehl
