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:48] – torsten.roehl | projekt:python_fastapi [2026/02/23 07:26] (aktuell) – [Systemd] torsten.roehl | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | ====== | + | ====== Python FASTAPI |
| [[raspberry_pi: | [[raspberry_pi: | ||
| + | //In diesem Projekt wird auf dem Raspberry Pi eine Weboberfläche mit FastAPI erstellt, über die eine LED-Ampel geschaltet und die Temperatur eines DS18B20 angezeigt werden kann. Die Anwendung ist im lokalen Netzwerk erreichbar, sodass LEDs und Temperatursensor bequem über einen Webbrowser im LAN gesteuert und überwacht werden können.// | ||
| - | ====== | + | |{{ : |
| + | |Die LED-Ampel kann nun über den Webbrowser gesteuert und die Temperatur ausgelesen werden.| | ||
| + | ====== | ||
| + | * Voraussetzungen | ||
| + | * Software | ||
| + | * Konfiguration | ||
| - | Ziel: | + | ====== Details ====== |
| - | * Klare Trennung: Hardware / Web / HTML | + | |
| - | * Keine Template-Engine | + | |
| - | * HTML als eigene Datei | + | |
| - | * Einmalige Initialisierung | + | |
| - | * Reboot-fest über systemd | + | |
| - | * VENV: ~/ | + | |
| + | ===== Voraussetzungen ===== | ||
| - | ===== 1. PROJEKTORDNER ===== | + | ==== 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 57: | 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 77: | 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 98: | 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 133: | 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 149: | Zeile 259: | ||
| from core import hardware | from core import hardware | ||
| - | app = FastAPI(root_path="/ | + | app = FastAPI() |
| Zeile 157: | 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 178: | 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 187: | 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 |
| + | ProxyPassReverse /led | ||
| + | |||
| + | ProxyPass | ||
| + | ProxyPassReverse /temp | ||
| </ | </ | ||
| + | |||
| + | === 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 | ||
| - | * Reboot-fest über systemd | ||
| - | |||
| - | |||
projekt/python_fastapi.1771613303.txt.gz · Zuletzt geändert: von torsten.roehl
