Compare commits
No commits in common. "claude/arduino-unity-simulation-values-90d508" and "main" have entirely different histories.
claude/ard
...
main
10
README.md
10
README.md
|
|
@ -10,12 +10,8 @@ Sensoren (Arduino UNO) ➔ WiFi/Bluetooth ➔ Backend (Aggregation & Regeln) ➔
|
||||||
Aktuell umgesetzt:
|
Aktuell umgesetzt:
|
||||||
```
|
```
|
||||||
DHT11 + CCS811 ─▶ Arduino Uno ─Serial (Pin 0/1)─▶ ESP-01 ─WLAN (Handy-Hotspot)─▶ Laptop: backend/server.py :3000 ◀── Browser
|
DHT11 + CCS811 ─▶ Arduino Uno ─Serial (Pin 0/1)─▶ ESP-01 ─WLAN (Handy-Hotspot)─▶ Laptop: backend/server.py :3000 ◀── Browser
|
||||||
▲
|
|
||||||
└──▶ Unity-Zwilling (SmartLectureRoom)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Unity-Zwilling (eigenes Repo `SmartLectureRoom`) holt im Modus **Verbindung → Backend** jede Sekunde `GET /api/messwerte` ab. Temperatur und eCO₂ vom Arduino ersetzen dann die simulierten Werte, das passende Szenario (Hitze, stickige Luft, Wintermorgen …) wird erkannt und Fenster, Lüftung, Heizung, Jalousie und Licht werden automatisch angesteuert. Die Entscheidung meldet Unity per `POST /api/steuerung` zurück. Details: README im Unity-Projekt, Abschnitt „Arduino anbinden“.
|
|
||||||
|
|
||||||
## Repo-Struktur
|
## Repo-Struktur
|
||||||
| Ordner | Inhalt |
|
| Ordner | Inhalt |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -25,7 +21,7 @@ Der Unity-Zwilling (eigenes Repo `SmartLectureRoom`) holt im Modus **Verbindung
|
||||||
| `frontend/` | Responsive Webinterface (Live-Werte, Präferenz-Slider) und 3D-Raum |
|
| `frontend/` | Responsive Webinterface (Live-Werte, Präferenz-Slider) und 3D-Raum |
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
Live-Messwerte laufen Ende-zu-Ende: Der Arduino misst Temperatur, Luftfeuchte, eCO₂ und TVOC, zeigt sie auf dem LCD und sendet sie per ESP-01 an `backend/server.py`; die Website (`frontend/`) zeigt sie live mit Verlauf und CO₂-Alarm. Der Unity-Zwilling ist an den Server angebunden (mit `simulator.py` vorbereitet, mit dem echten Arduino noch nicht getestet). Aggregation/Präferenzen im Backend fehlen noch (siehe [docs/offene-punkte.md](docs/offene-punkte.md)).
|
Live-Messwerte laufen Ende-zu-Ende: Der Arduino misst Temperatur, Luftfeuchte, eCO₂ und TVOC, zeigt sie auf dem LCD und sendet sie per ESP-01 an `backend/server.py`; die Website (`frontend/`) zeigt sie live mit Verlauf und CO₂-Alarm. Aggregation/Präferenzen und 3D-Zwilling fehlen noch (siehe [docs/offene-punkte.md](docs/offene-punkte.md)).
|
||||||
|
|
||||||
## Komponenten und ihre Aufgabe
|
## Komponenten und ihre Aufgabe
|
||||||
|
|
||||||
|
|
@ -48,10 +44,10 @@ Live-Messwerte laufen Ende-zu-Ende: Der Arduino misst Temperatur, Luftfeuchte, e
|
||||||
| `firmware/dht11_lcd/dht11_lcd.ino` | **Hauptprogramm** auf dem Uno: Sensoren lesen, LCD anzeigen, Senden anstoßen. Speichert außerdem stündlich die Kalibrierung („Baseline“) des CCS811 im EEPROM, damit die Werte nach einem Neustart schneller stimmen. |
|
| `firmware/dht11_lcd/dht11_lcd.ino` | **Hauptprogramm** auf dem Uno: Sensoren lesen, LCD anzeigen, Senden anstoßen. Speichert außerdem stündlich die Kalibrierung („Baseline“) des CCS811 im EEPROM, damit die Werte nach einem Neustart schneller stimmen. |
|
||||||
| `firmware/dht11_lcd/esp_wifi.h` | Steuert den ESP: Baudrate finden, mit dem Hotspot verbinden, Messwerte als **HTTP-POST** an den Laptop schicken. |
|
| `firmware/dht11_lcd/esp_wifi.h` | Steuert den ESP: Baudrate finden, mit dem Hotspot verbinden, Messwerte als **HTTP-POST** an den Laptop schicken. |
|
||||||
| `firmware/dht11_lcd/wifi_config.h` | Zugangsdaten (Hotspot-Name, Passwort, Laptop-IP). Nicht im Repo, Vorlage: `wifi_config.example.h`. |
|
| `firmware/dht11_lcd/wifi_config.h` | Zugangsdaten (Hotspot-Name, Passwort, Laptop-IP). Nicht im Repo, Vorlage: `wifi_config.example.h`. |
|
||||||
| `backend/server.py` | **Server** auf dem Laptop (Port 3000): nimmt die Messwerte vom ESP an, merkt sich die letzten 100, liefert die Website aus und stellt die Werte dem Unity-Zwilling bereit (dessen Steuerbefehle kommen per `POST /api/steuerung` zurück). |
|
| `backend/server.py` | **Server** auf dem Laptop (Port 3000): nimmt die Messwerte vom ESP an, merkt sich die letzten 100 und liefert die Website aus. |
|
||||||
| `backend/start.bat` | Startet den Server per Doppelklick und prüft, ob die Laptop-IP zu `wifi_config.h` passt. |
|
| `backend/start.bat` | Startet den Server per Doppelklick und prüft, ob die Laptop-IP zu `wifi_config.h` passt. |
|
||||||
| `backend/firewall_freigeben.bat` | Gibt Port 3000 in der Windows-Firewall frei, damit der ESP den Laptop erreicht. |
|
| `backend/firewall_freigeben.bat` | Gibt Port 3000 in der Windows-Firewall frei, damit der ESP den Laptop erreicht. |
|
||||||
| `backend/simulator.py` | Schickt erfundene Werte – zum Testen ohne Arduino. Mit `--szenario hitze/stickig/kalt/normal/demo` gezielt Szenarien für den Unity-Zwilling auslösen. |
|
| `backend/simulator.py` | Schickt erfundene Werte – zum Testen ohne Arduino. |
|
||||||
| `frontend/` | **Website**: fragt alle 2 s beim Server die neuesten Werte ab und zeigt Kacheln, Verlaufsdiagramm und CO₂-Alarm ab 1000 ppm. |
|
| `frontend/` | **Website**: fragt alle 2 s beim Server die neuesten Werte ab und zeigt Kacheln, Verlaufsdiagramm und CO₂-Alarm ab 1000 ppm. |
|
||||||
| `firmware/esp_test`, `firmware/ccs811_test`, `firmware/ccs811_diagnose` | Testprogramme zur Fehlersuche, im normalen Betrieb nicht nötig. |
|
| `firmware/esp_test`, `firmware/ccs811_test`, `firmware/ccs811_diagnose` | Testprogramme zur Fehlersuche, im normalen Betrieb nicht nötig. |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ Minimaler Server mit Python-Bordmitteln (Python ≥ 3.8, kein `pip install`, lä
|
||||||
|
|
||||||
```
|
```
|
||||||
ESP8266 ──POST /api/messwerte──▶ backend/server.py ◀──GET /api/messwerte── Browser (frontend/)
|
ESP8266 ──POST /api/messwerte──▶ backend/server.py ◀──GET /api/messwerte── Browser (frontend/)
|
||||||
▲ ◀──GET /api/messwerte── Unity-Zwilling
|
|
||||||
└───────────POST /api/steuerung─── Unity-Zwilling
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Starten
|
## Starten
|
||||||
|
|
@ -13,9 +11,7 @@ Doppelklick auf `start.bat` oder:
|
||||||
```bash
|
```bash
|
||||||
python backend/server.py # Port 3000 (oder: python backend/server.py 8080)
|
python backend/server.py # Port 3000 (oder: python backend/server.py 8080)
|
||||||
python backend/simulator.py # optional: Fake-Sensordaten ohne Hardware
|
python backend/simulator.py # optional: Fake-Sensordaten ohne Hardware
|
||||||
python backend/simulator.py --szenario demo # Szenarien nacheinander: normal, stickig, normal, hitze, kalt
|
|
||||||
```
|
```
|
||||||
Szenarien des Simulators: `wechsel` (Standard, Werte schwanken), `normal`, `hitze` (→ ca. 30 °C), `stickig` (→ ca. 1600 ppm), `kalt` (→ ca. 17 °C), `demo`. Die Werte ändern sich langsam wie in echt, damit man im Unity-Zwilling sieht, wann die Steuerung umschaltet.
|
|
||||||
Frontend: http://localhost:3000, im Hotspot von anderen Geräten: `http://<Laptop-IP>:3000`
|
Frontend: http://localhost:3000, im Hotspot von anderen Geräten: `http://<Laptop-IP>:3000`
|
||||||
|
|
||||||
Beim Start zeigt der Server die IP-Adressen des Laptops und prüft, ob `SERVER_IP` in `firmware/dht11_lcd/wifi_config.h` dazu passt („OK“ bzw. „ACHTUNG“). Jeder empfangene Messwert erscheint mit Absender-IP im Fenster (`127.0.0.1` = Simulator, `172.20.10.x` = ESP im Hotspot).
|
Beim Start zeigt der Server die IP-Adressen des Laptops und prüft, ob `SERVER_IP` in `firmware/dht11_lcd/wifi_config.h` dazu passt („OK“ bzw. „ACHTUNG“). Jeder empfangene Messwert erscheint mit Absender-IP im Fenster (`127.0.0.1` = Simulator, `172.20.10.x` = ESP im Hotspot).
|
||||||
|
|
@ -41,22 +37,6 @@ Alle Felder sind Zahlen; ein Feld darf fehlen oder `null` sein (z. B. CCS811 noc
|
||||||
|
|
||||||
`GET /api/messwerte` liefert `{"aktuell": {...} | null, "verlauf": [...]}`.
|
`GET /api/messwerte` liefert `{"aktuell": {...} | null, "verlauf": [...]}`.
|
||||||
|
|
||||||
### Steuerbefehle vom Unity-Zwilling
|
|
||||||
`POST /api/steuerung` (sendet Unity im Modus „Backend“, sobald sich etwas ändert, sonst alle 10 s):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"window": "open", "blind": 1.0, "heat": 0.0, "vent": 0.0, "light": 0.4, "beamer": 0, "szenario": "Hitze"}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Feld | Bedeutung |
|
|
||||||
|---|---|
|
|
||||||
| `window` | `closed`, `tilted` oder `open` |
|
|
||||||
| `blind`, `heat`, `vent`, `light` | Sollwert 0 … 1 (Jalousie unten, Heizung, Lüftungsanlage, Licht) |
|
|
||||||
| `beamer` | 0/1 |
|
|
||||||
| `szenario` | vom Zwilling erkanntes Szenario |
|
|
||||||
|
|
||||||
Der Server merkt sich nur den letzten Stand (`GET /api/steuerung` → `{"aktuell": {...} | null}`) und gibt ihn im Fenster aus. Echte Aktoren gibt es noch nicht.
|
|
||||||
|
|
||||||
Test ohne Hardware (PowerShell):
|
Test ohne Hardware (PowerShell):
|
||||||
```powershell
|
```powershell
|
||||||
Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/messwerte -ContentType "application/json" `
|
Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/messwerte -ContentType "application/json" `
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
# Minimaler Empfangs-Server: ESP8266 -> POST /api/messwerte -> Website/Unity-Zwilling (GET /api/messwerte)
|
# Minimaler Empfangs-Server: ESP8266 -> POST /api/messwerte -> Website (GET /api/messwerte)
|
||||||
# Der Unity-Zwilling meldet seine Steuerentscheidung per POST /api/steuerung zurueck (GET liefert sie).
|
|
||||||
# Nur Python-Bordmittel, kein pip install noetig. Start: python server.py [Port] (oder start.bat)
|
# Nur Python-Bordmittel, kein pip install noetig. Start: python server.py [Port] (oder start.bat)
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
|
@ -23,11 +22,6 @@ FELDER = ["temperatur", "luftfeuchte", "eco2", "tvoc"]
|
||||||
|
|
||||||
verlauf = deque(maxlen=MAX_VERLAUF) # { zeit: ISO-String, temperatur, luftfeuchte, eco2, tvoc }
|
verlauf = deque(maxlen=MAX_VERLAUF) # { zeit: ISO-String, temperatur, luftfeuchte, eco2, tvoc }
|
||||||
|
|
||||||
# Steuerbefehle des Unity-Zwillings, z. B. {"window":"open","blind":1.0,"heat":0,"vent":0,"light":0.4,"beamer":0,"szenario":"Hitze"}
|
|
||||||
STEUER_TEXTFELDER = {"window", "szenario"}
|
|
||||||
STEUER_ZAHLFELDER = {"blind", "heat", "vent", "light", "beamer"}
|
|
||||||
steuerung = None
|
|
||||||
|
|
||||||
MIME = {
|
MIME = {
|
||||||
".html": "text/html; charset=utf-8",
|
".html": "text/html; charset=utf-8",
|
||||||
".js": "text/javascript; charset=utf-8",
|
".js": "text/javascript; charset=utf-8",
|
||||||
|
|
@ -49,31 +43,19 @@ class Handler(BaseHTTPRequestHandler):
|
||||||
def pfad(self):
|
def pfad(self):
|
||||||
return self.path.split("?", 1)[0]
|
return self.path.split("?", 1)[0]
|
||||||
|
|
||||||
def lies_json_objekt(self):
|
|
||||||
"""Liest den Body als JSON-Objekt. Bei Fehlern wird die Antwort gesendet und None geliefert."""
|
|
||||||
laenge = int(self.headers.get("Content-Length") or 0)
|
|
||||||
if laenge > MAX_BODY_BYTES:
|
|
||||||
self.sende_json(413, {"fehler": "Anfrage zu gross"})
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
daten = json.loads(self.rfile.read(laenge))
|
|
||||||
except (ValueError, UnicodeDecodeError):
|
|
||||||
self.sende_json(400, {"fehler": "Ungueltiges JSON"})
|
|
||||||
return None
|
|
||||||
if not isinstance(daten, dict):
|
|
||||||
self.sende_json(400, {"fehler": "JSON-Objekt erwartet"})
|
|
||||||
return None
|
|
||||||
return daten
|
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
if self.pfad() == "/api/steuerung":
|
|
||||||
return self.steuerung_empfangen()
|
|
||||||
if self.pfad() != "/api/messwerte":
|
if self.pfad() != "/api/messwerte":
|
||||||
return self.sende_json(405, {"fehler": "Methode nicht erlaubt"})
|
return self.sende_json(405, {"fehler": "Methode nicht erlaubt"})
|
||||||
|
|
||||||
daten = self.lies_json_objekt()
|
laenge = int(self.headers.get("Content-Length") or 0)
|
||||||
if daten is None:
|
if laenge > MAX_BODY_BYTES:
|
||||||
return
|
return self.sende_json(413, {"fehler": "Anfrage zu gross"})
|
||||||
|
try:
|
||||||
|
daten = json.loads(self.rfile.read(laenge))
|
||||||
|
except (ValueError, UnicodeDecodeError):
|
||||||
|
return self.sende_json(400, {"fehler": "Ungueltiges JSON"})
|
||||||
|
if not isinstance(daten, dict):
|
||||||
|
return self.sende_json(400, {"fehler": "JSON-Objekt erwartet"})
|
||||||
|
|
||||||
eintrag = {"zeit": datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")}
|
eintrag = {"zeit": datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")}
|
||||||
for feld in FELDER:
|
for feld in FELDER:
|
||||||
|
|
@ -90,27 +72,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||||
print(datetime.now().strftime("%H:%M:%S"), self.client_address[0], json.dumps(eintrag), flush=True)
|
print(datetime.now().strftime("%H:%M:%S"), self.client_address[0], json.dumps(eintrag), flush=True)
|
||||||
self.sende_json(200, {"ok": True})
|
self.sende_json(200, {"ok": True})
|
||||||
|
|
||||||
def steuerung_empfangen(self):
|
|
||||||
global steuerung
|
|
||||||
daten = self.lies_json_objekt()
|
|
||||||
if daten is None:
|
|
||||||
return
|
|
||||||
eintrag = {"zeit": datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")}
|
|
||||||
for feld, wert in daten.items():
|
|
||||||
if feld in STEUER_TEXTFELDER and isinstance(wert, str):
|
|
||||||
eintrag[feld] = wert[:40]
|
|
||||||
elif feld in STEUER_ZAHLFELDER and isinstance(wert, (int, float)) and not isinstance(wert, bool) and math.isfinite(wert):
|
|
||||||
eintrag[feld] = wert
|
|
||||||
steuerung = eintrag
|
|
||||||
print(datetime.now().strftime("%H:%M:%S"), "Unity-Steuerung", json.dumps(eintrag, ensure_ascii=False), flush=True)
|
|
||||||
self.sende_json(200, {"ok": True})
|
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
pfad = self.pfad()
|
pfad = self.pfad()
|
||||||
if pfad == "/api/messwerte":
|
if pfad == "/api/messwerte":
|
||||||
return self.sende_json(200, {"aktuell": verlauf[-1] if verlauf else None, "verlauf": list(verlauf)})
|
return self.sende_json(200, {"aktuell": verlauf[-1] if verlauf else None, "verlauf": list(verlauf)})
|
||||||
if pfad == "/api/steuerung":
|
|
||||||
return self.sende_json(200, {"aktuell": steuerung})
|
|
||||||
|
|
||||||
datei = (FRONTEND_DIR / ("index.html" if pfad == "/" else pfad.lstrip("/"))).resolve()
|
datei = (FRONTEND_DIR / ("index.html" if pfad == "/" else pfad.lstrip("/"))).resolve()
|
||||||
if FRONTEND_DIR not in datei.parents:
|
if FRONTEND_DIR not in datei.parents:
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,27 @@
|
||||||
# Simuliert den ESP8266: schickt alle 2 s Fantasiewerte an den Server (Entwicklung ohne Hardware).
|
# Simuliert den ESP8266: schickt alle 2 s Fantasiewerte an den Server (Entwicklung ohne Hardware).
|
||||||
# Start: python simulator.py [Server-URL] [--szenario NAME] z. B. http://localhost:3000 --szenario hitze
|
# Start: python simulator.py [Server-URL] z. B. http://localhost:3000
|
||||||
#
|
|
||||||
# Szenarien (fuer die Vorfuehrung mit dem Unity-Zwilling, Modus "Backend"):
|
|
||||||
# wechsel (Standard) Temperatur und CO2 schwanken, CO2 ueberschreitet zeitweise 1000 ppm
|
|
||||||
# normal angenehmes Raumklima (22 °C, ca. 650 ppm)
|
|
||||||
# hitze Temperatur steigt langsam auf ca. 30 °C -> Unity oeffnet die Fenster
|
|
||||||
# stickig CO2 steigt langsam auf ca. 1600 ppm -> Unity lueftet (Fenster oder Lueftungsanlage)
|
|
||||||
# kalt Temperatur faellt auf ca. 17 °C -> Unity heizt
|
|
||||||
# demo laeuft nacheinander normal -> stickig -> normal -> hitze -> kalt durch (je ca. 1 min)
|
|
||||||
import argparse
|
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Fake-Messwerte wie vom ESP8266")
|
basis = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000"
|
||||||
parser.add_argument("basis", nargs="?", default="http://localhost:3000", help="Server-URL")
|
|
||||||
parser.add_argument("--szenario", default="wechsel", choices=["wechsel", "normal", "hitze", "stickig", "kalt", "demo"])
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
ZIELE = { # Zielwerte (Temperatur °C, eCO2 ppm), auf die sich die Werte langsam zubewegen
|
|
||||||
"normal": (22.0, 650),
|
|
||||||
"hitze": (30.0, 750),
|
|
||||||
"stickig": (23.0, 1600),
|
|
||||||
"kalt": (17.0, 600),
|
|
||||||
}
|
|
||||||
DEMO_ABLAUF = ["normal", "stickig", "normal", "hitze", "kalt"]
|
|
||||||
DEMO_SCHRITTE = 30 # 30 x 2 s = 1 min je Abschnitt
|
|
||||||
|
|
||||||
temperatur, eco2 = 22.0, 650.0
|
|
||||||
schritt = 0
|
schritt = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
schritt += 1
|
schritt += 1
|
||||||
if args.szenario == "wechsel":
|
|
||||||
temperatur = 22 + 2 * math.sin(schritt / 20)
|
|
||||||
eco2 = 800 + 400 * math.sin(schritt / 25) # ueberschreitet zeitweise 1000 ppm
|
|
||||||
aktiv = "wechsel"
|
|
||||||
else:
|
|
||||||
aktiv = args.szenario
|
|
||||||
if aktiv == "demo":
|
|
||||||
aktiv = DEMO_ABLAUF[(schritt // DEMO_SCHRITTE) % len(DEMO_ABLAUF)]
|
|
||||||
ziel_t, ziel_co2 = ZIELE[aktiv]
|
|
||||||
temperatur += max(-0.4, min(0.4, ziel_t - temperatur))
|
|
||||||
eco2 += max(-60.0, min(60.0, ziel_co2 - eco2))
|
|
||||||
|
|
||||||
messwert = {
|
messwert = {
|
||||||
"temperatur": round(temperatur + 0.1 * math.sin(schritt), 1),
|
"temperatur": round(22 + 2 * math.sin(schritt / 20), 1),
|
||||||
"luftfeuchte": round(45 + 5 * math.sin(schritt / 15), 1),
|
"luftfeuchte": round(45 + 5 * math.sin(schritt / 15), 1),
|
||||||
"eco2": round(eco2 + 10 * math.sin(schritt / 3)),
|
"eco2": round(800 + 400 * math.sin(schritt / 25)), # ueberschreitet zeitweise 1000 ppm
|
||||||
"tvoc": round(70 + 30 * math.sin(schritt / 10) + max(0.0, eco2 - 800) / 10),
|
"tvoc": round(70 + 30 * math.sin(schritt / 10)),
|
||||||
}
|
}
|
||||||
anfrage = urllib.request.Request(args.basis + "/api/messwerte", data=json.dumps(messwert).encode(),
|
anfrage = urllib.request.Request(basis + "/api/messwerte", data=json.dumps(messwert).encode(),
|
||||||
headers={"Content-Type": "application/json"})
|
headers={"Content-Type": "application/json"})
|
||||||
try:
|
try:
|
||||||
urllib.request.urlopen(anfrage, timeout=3).read()
|
urllib.request.urlopen(anfrage, timeout=3).read()
|
||||||
print(f"gesendet [{aktiv}]", json.dumps(messwert))
|
print("gesendet", json.dumps(messwert))
|
||||||
except OSError as fehler:
|
except OSError as fehler:
|
||||||
print("Server nicht erreichbar:", fehler)
|
print("Server nicht erreichbar:", fehler)
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue