Benutzer-Werkzeuge

Webseiten-Werkzeuge


projekt:python_fastapi1

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen RevisionVorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
projekt:python_fastapi1 [2026/02/21 11:53] torsten.roehlprojekt:python_fastapi1 [2026/02/21 13:22] (aktuell) – gelöscht torsten.roehl
Zeile 1: Zeile 1:
-OK. MINIMAL-ERWEITERUNG OHNE JAVASCRIPT/CHART.JS: 
-- Verlauf wird als TEXTDATEI gespeichert (course_web/data/temperature.txt) 
-- Aufzeichnung läuft im Hintergrund (Thread) im laufenden FastAPI-Prozess (also auch ohne geöffneten Browser) 
-- Anzeige im Browser: einfaches HTML + vorformatierter Text (letzte N Zeilen), plus Start/Stop/Reset/Intervall 
-- “API” bleibt in hardware.py: dort kommen NUR einfache History-Methoden dazu 
-- app.py bekommt nur neue Routen + startet den Collector-Thread (keine “komische” Logik im HTML) 
  
-================================================================================ 
-1) PROJEKTSTRUKTUR (wie bei dir) 
-course_web/ 
-|── data/ 
-|   └── temperature.txt 
-└── src/ 
-    ├── app.py 
-    ├── core/ 
-    │   ├── __init__.py 
-    │   └── hardware.py 
-    └── html/ 
-        ├── led.html 
-        ├── temp.html 
-        └── history.html 
-================================================================================ 
- 
-================================================================================ 
-2) DATEI: /home/pi/devel/projects/course_web/src/core/hardware.py 
-ERGÄNZEN (unten dranhängen, bestehender Code bleibt, KEIN Umbau der vorhandenen Funktionen) 
-================================================================================ 
-- Ziel: simple Methoden: 
-  - history_append(temp, file) 
-  - history_read(file, max_lines) 
-  - history_reset(file) 
-  - history_set_recording(True/False) 
-  - history_set_interval_minutes(x) 
-  - history_start_collector(get_temperature_func) 
-  - history_status() 
- 
-<code python /home/pi/devel/projects/course_web/src/core/hardware.py> 
-# ============================= 
-# Temperaturverlauf (History) 
-# ============================= 
-import os 
-import threading 
-from datetime import datetime 
- 
-HISTORY_FILE_DEFAULT = "/home/pi/devel/projects/course_web/data/temperature.txt" 
- 
-_history_lock = threading.Lock() 
-_history_recording = False 
-_history_interval_seconds = 60 
-_history_thread_started = False 
- 
- 
-def history_status(): 
-    with _history_lock: 
-        return { 
-            "recording": _history_recording, 
-            "interval_seconds": _history_interval_seconds, 
-        } 
- 
- 
-def history_set_recording(on: bool): 
-    global _history_recording 
-    with _history_lock: 
-        _history_recording = bool(on) 
- 
- 
-def history_set_interval_minutes(minutes: int): 
-    global _history_interval_seconds 
-    if minutes < 1: 
-        raise ValueError("minutes must be >= 1") 
-    with _history_lock: 
-        _history_interval_seconds = int(minutes) * 60 
- 
- 
-def history_reset(file_path: str = HISTORY_FILE_DEFAULT): 
-    os.makedirs(os.path.dirname(file_path), exist_ok=True) 
-    with open(file_path, "w") as f: 
-        f.write("") 
- 
- 
-def history_append(temp_c: float, file_path: str = HISTORY_FILE_DEFAULT): 
-    os.makedirs(os.path.dirname(file_path), exist_ok=True) 
-    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 
-    with open(file_path, "a") as f: 
-        f.write(f"{ts};{temp_c:.3f}\n") 
- 
- 
-def history_read(file_path: str = HISTORY_FILE_DEFAULT, max_lines: int = 200): 
-    # gibt Liste von Zeilen (Strings) zurück, ohne Parsing-Zauber 
-    try: 
-        with open(file_path, "r") as f: 
-            lines = [ln.rstrip("\n") for ln in f.readlines() if ln.strip()] 
-    except FileNotFoundError: 
-        return [] 
- 
-    if max_lines is not None and max_lines > 0: 
-        return lines[-max_lines:] 
-    return lines 
- 
- 
-def history_start_collector(get_temp_func, file_path: str = HISTORY_FILE_DEFAULT): 
-    # get_temp_func: callable -> float|None 
-    global _history_thread_started 
- 
-    def _loop(): 
-        while True: 
-            with _history_lock: 
-                rec = _history_recording 
-                sleep_s = _history_interval_seconds 
- 
-            if rec: 
-                try: 
-                    t = get_temp_func() 
-                    if t is not None: 
-                        history_append(float(t), file_path=file_path) 
-                except Exception: 
-                    # bewusst still, damit Anfänger nicht “zufällig” alles crashen 
-                    # (wenn gewünscht: print(...) oder logging später) 
-                    pass 
- 
-            time.sleep(sleep_s) 
- 
-    with _history_lock: 
-        if _history_thread_started: 
-            return 
-        th = threading.Thread(target=_loop, daemon=True) 
-        th.start() 
-        _history_thread_started = True 
-</code> 
- 
-WICHTIG: Oben in hardware.py ist bereits "import time" vorhanden. Das wird im Collector gebraucht. 
-================================================================================ 
- 
-================================================================================ 
-3) DATEI: /home/pi/devel/projects/course_web/src/html/history.html 
-OHNE JAVASCRIPT, NUR HTML (Status + Buttons + Textausgabe) 
-================================================================================ 
-<code html /home/pi/devel/projects/course_web/src/html/history.html> 
-<!DOCTYPE html> 
-<html> 
-<head> 
-    <title>Temperaturverlauf</title> 
-    <meta charset="utf-8" /> 
-</head> 
-<body> 
- 
-<h1>Temperaturverlauf</h1> 
- 
-<p><b>Status:</b> {{REC}} | <b>Intervall:</b> {{INT}} s</p> 
- 
-<h2>Steuerung</h2> 
-<a href="/history/start">Start</a> 
-<a href="/history/stop">Stop</a> 
-<a href="/history/reset">Reset</a> 
- 
-<h2>Intervall (Minuten)</h2> 
-<a href="/history/interval/1">1</a> 
-<a href="/history/interval/5">5</a> 
-<a href="/history/interval/10">10</a> 
- 
-<h2>Datei-Auszug (letzte {{N}} Zeilen)</h2> 
-<pre>{{LINES}}</pre> 
- 
-<br><br> 
-<a href="/">Zurück</a> 
- 
-</body> 
-</html> 
-</code> 
-================================================================================ 
- 
-================================================================================ 
-4) DATEI: /home/pi/devel/projects/course_web/src/app.py 
-ERWEITERN (bestehende Routen bleiben; nur History dazu) 
-================================================================================ 
-- Startup: hardware.init() bleibt 
-- zusätzlich: hardware.history_start_collector(hardware.get_temperature) 
-- neue Routen: 
-  - /history 
-  - /history/start 
-  - /history/stop 
-  - /history/reset 
-  - /history/interval/{minutes} 
- 
-<code python /home/pi/devel/projects/course_web/src/app.py> 
-from fastapi import FastAPI, HTTPException 
-from fastapi.responses import HTMLResponse, RedirectResponse 
-from core import hardware 
- 
-app = FastAPI() 
- 
- 
-@app.on_event("startup") 
-def startup(): 
-    hardware.init() 
-    hardware.history_start_collector(hardware.get_temperature) 
- 
- 
-def load_template(name, replacements): 
-    try: 
-        with open(f"html/{name}", "r") as f: 
-            html = f.read() 
-    except FileNotFoundError: 
-        raise HTTPException(status_code=500, detail="Template nicht gefunden") 
- 
-    for key, value in replacements.items(): 
-        html = html.replace(key, str(value)) 
- 
-    return html 
- 
- 
-@app.get("/led", response_class=HTMLResponse) 
-def led_page(): 
-    r, y, g = hardware.status() 
-    return HTMLResponse( 
-        load_template("led.html", { 
-            "{{R}}": r, 
-            "{{Y}}": y, 
-            "{{G}}": g 
-        }) 
-    ) 
- 
- 
-@app.get("/led/{color}/{value}") 
-def set_led(color: str, value: int): 
- 
-    if value not in (0, 1): 
-        raise HTTPException(status_code=400) 
- 
-    if color == "r": 
-        hardware.setRedLED(value) 
-    elif color == "y": 
-        hardware.setYellowLED(value) 
-    elif color == "g": 
-        hardware.setGreenLED(value) 
-    else: 
-        raise HTTPException(status_code=400) 
- 
-    return RedirectResponse(url="/led", status_code=303) 
- 
- 
-@app.get("/temp", response_class=HTMLResponse) 
-def temp_page(): 
-    t = hardware.get_temperature() 
- 
-    if t is None: 
-        value = "Sensorfehler" 
-    else: 
-        value = f"{t:.2f}" 
- 
-    return HTMLResponse( 
-        load_template("temp.html", { 
-            "{{T}}": value 
-        }) 
-    ) 
- 
- 
-# ============================= 
-# Temperaturverlauf (History) 
-# ============================= 
- 
-@app.get("/history", response_class=HTMLResponse) 
-def history_page(): 
-    st = hardware.history_status() 
-    lines = hardware.history_read(max_lines=200) 
- 
-    rec_txt = "läuft" if st["recording"] else "stopp" 
-    int_s = st["interval_seconds"] 
- 
-    return HTMLResponse( 
-        load_template("history.html", { 
-            "{{REC}}": rec_txt, 
-            "{{INT}}": int_s, 
-            "{{N}}": 200, 
-            "{{LINES}}": "\n".join(lines) if lines else "(keine Daten - Start drücken)", 
-        }) 
-    ) 
- 
- 
-@app.get("/history/start") 
-def history_start(): 
-    hardware.history_set_recording(True) 
-    return RedirectResponse(url="/history", status_code=303) 
- 
- 
-@app.get("/history/stop") 
-def history_stop(): 
-    hardware.history_set_recording(False) 
-    return RedirectResponse(url="/history", status_code=303) 
- 
- 
-@app.get("/history/reset") 
-def history_reset(): 
-    hardware.history_reset() 
-    return RedirectResponse(url="/history", status_code=303) 
- 
- 
-@app.get("/history/interval/{minutes}") 
-def history_interval(minutes: int): 
-    try: 
-        hardware.history_set_interval_minutes(minutes) 
-    except ValueError: 
-        raise HTTPException(status_code=400) 
-    return RedirectResponse(url="/history", status_code=303) 
-</code> 
- 
-HINWEIS: Das ist “ohne Umbau”: nur ergänzt. 
-================================================================================ 
- 
-================================================================================ 
-5) APACHE STARTSEITE index.html 
-NUR LINK ERGÄNZEN 
-================================================================================ 
-- in /var/www/html/index.html ergänzen: 
-  <li><a href="/history">Temperaturverlauf</a></li> 
- 
-================================================================================ 
-6) APACHE PROXY (WICHTIG: PREFIX /history/ ) 
-================================================================================ 
-In /etc/apache2/sites-available/000-default.conf innerhalb <VirtualHost *:80>: 
- 
-<code> 
-ProxyPreserveHost On 
- 
-ProxyPass        /led      http://127.0.0.1:8000/led 
-ProxyPassReverse /led      http://127.0.0.1:8000/led 
- 
-ProxyPass        /temp     http://127.0.0.1:8000/temp 
-ProxyPassReverse /temp     http://127.0.0.1:8000/temp 
- 
-ProxyPass        /history/  http://127.0.0.1:8000/history/ 
-ProxyPassReverse /history/  http://127.0.0.1:8000/history/ 
-ProxyPass        /history   http://127.0.0.1:8000/history 
-ProxyPassReverse /history   http://127.0.0.1:8000/history 
-</code> 
- 
-(Die 2 letzten Zeilen sind für /history ohne Slash; der Prefix ist für /history/start usw.) 
-================================================================================ 
- 
-================================================================================ 
-7) SYSTEMD bleibt wie gehabt (course_web.service) 
-================================================================================ 
- 
-================================================================================ 
-8) TEST (ohne “Sonderweg”) 
-================================================================================ 
-1) Browser: http://<PI-IP>/history 
-2) Start klicken 
-3) 1–2 Minuten warten (oder Intervall 1) 
-4) Seite neu laden: Zeilen erscheinen 
-5) Stop -> es kommen keine neuen Zeilen 
-6) Reset -> Datei leer 
- 
-CLI-Check: 
-<code bash> 
-tail -n 20 /home/pi/devel/projects/course_web/data/temperature.txt 
-</code> 
- 
-================================================================================ 
-DAS IST DIE “SIMPLE METHODEN IN hardware.py”-VARIANTE: 
-- kein chart.js 
-- kein javascript 
-- keine daten “nur wenn browser offen” 
-- Start/Stop/Reset/Intervall drin 
-- Anzeige grafisch? NEIN (bewusst), weil sonst JS/PNG/Libs nötig wären 
- 
-Wenn “grafisch” zwingend ist, dann gibt’s genau 2 saubere Anfänger-Optionen: 
-A) serverseitig PNG (matplotlib) + <img src="/history/plot.png">  (ohne JS) 
-B) JS im Browser (Chart.js)  (wie du NICHT willst) 
projekt/python_fastapi1.1771674820.txt.gz · Zuletzt geändert: von torsten.roehl