Files
kxgauge/main.py
viewit 65072b856a Initial commit: tfteye Gesichts-Animation fuer ESP32-C3/GC9A01
24 Emotionen, HTTP-API, HeatRing-Temperaturanzeige mit Farbverlauf,
Trapez-Lider (Cozmo-inspiriert), Tests per curl.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 10:01:57 +02:00

139 lines
4.6 KiB
Python

from machine import Pin, SPI
import gc9a01
import network
import time
from eyes import Eyes
from mouth import Mouth
from heat_ring import HeatRing
from api_server import ApiServer
# --- WLAN verbinden ---
# Zugangsdaten liegen in secrets.py (nicht committen). Fehlt die
# Datei, laeuft das Gesicht offline weiter -- nur ohne API.
try:
from secrets import WIFI_SSID, WIFI_PASSWORD
except ImportError:
WIFI_SSID = None
WIFI_PASSWORD = None
print("secrets.py fehlt - starte ohne WLAN")
wlan = network.WLAN(network.STA_IF)
if WIFI_SSID:
wlan.active(True)
# Power-Save aus: der ESP32 sonst zeitweise komplett unerreichbar
# (kein ARP, kein Ping), bis ein Paket den Funk wieder aufweckt --
# bei Discovery-Probes reicht das, bei der Gesichts-API stoerts.
wlan.config(pm=wlan.PM_NONE)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
print("Verbinde mit WLAN...")
timeout_ms = 15000
start = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), start) > timeout_ms:
print("WLAN-Verbindung fehlgeschlagen, fahre ohne API fort")
break
time.sleep_ms(200)
if wlan.isconnected():
print("WLAN verbunden:", wlan.ifconfig()[0])
else:
print("Kein WLAN - API-Server startet nicht")
# --- Display-Init (ESP32-C3 Super Mini, siehe agents.md fuer Pinbelegung) ---
rst = Pin(0, Pin.OUT)
rst.value(1)
time.sleep_ms(50)
rst.value(0)
time.sleep_ms(50)
rst.value(1)
time.sleep_ms(150)
spi = SPI(1, baudrate=40000000, sck=Pin(2), mosi=Pin(4))
tft = gc9a01.GC9A01(
spi,
240, 240,
reset=Pin(0, Pin.OUT),
dc=Pin(1, Pin.OUT),
cs=Pin(5, Pin.OUT),
)
tft.init()
tft.fill(0x0000)
# --- Augen und Mund starten ---
face_color = gc9a01.color565(80, 200, 255) # helles Techno-Blau
eyes = Eyes(tft, eye_color=face_color)
mouth = Mouth(tft, y_offset=50, color=face_color)
heat_ring = HeatRing(tft)
def set_face_emotion(name):
"""Setzt Emotion auf Augen UND Mund gleichzeitig -- zentrale
Stelle, damit API und lokale Steuerung nicht getrennt aufgerufen
werden muessen."""
eyes.set_emotion(name)
mouth.set_emotion(name)
set_face_emotion("NEUTRAL")
# --- API-Server starten (nur falls WLAN verbunden) ---
api = None
if wlan.isconnected():
try:
api = ApiServer(eyes, mouth_ref=mouth, heat_ring_ref=heat_ring, port=80)
print("API laeuft auf http://%s/" % wlan.ifconfig()[0])
print("Beispiel: http://%s/emotion/happy" % wlan.ifconfig()[0])
except Exception as e:
print("API-Server konnte nicht gestartet werden:", e)
import gc
gc.collect()
print("RAM frei nach Init: %d Bytes (~%.1f KB)" % (gc.mem_free(), gc.mem_free() / 1024))
# --- Hauptschleife ---
# WLAN-Reconnect: wlan.connect() verbindet sich nur EINMAL beim Boot.
# Bricht die Verbindung spaeter ab (Funkstoerung, AP-Roaming, ...),
# bleibt das Board sonst dauerhaft offline bis zum naechsten Reset.
# Pruefung alle 5s statt jeden Frame -- wlan.isconnected() ist billig,
# aber taeglich zigtausendfach pro Sekunde unnoetig.
RECONNECT_INTERVAL_MS = 5000
next_wlan_check = time.ticks_add(time.ticks_ms(), RECONNECT_INTERVAL_MS)
while True:
eyes.update()
mouth.update()
heat_ring.update()
# Schweiss folgt dem aktuellen Ringstand direkt (nicht dem Ziel-
# wert), damit es zusammen mit dem Ring langsam ansteigt statt
# sofort zu springen. Sonnenbrille an, sobald der aktuelle Stand
# die per /heat/peak gesetzte Zieltemperatur erreicht (oder 1.0,
# falls kein Peak gesetzt ist).
eyes.set_heat(heat_ring.cur_level)
peak_target = heat_ring.peak_level if heat_ring.peak_level is not None else 1.0
eyes.set_sunglasses_auto(heat_ring.cur_level >= peak_target - 0.01)
if api is not None:
api.poll()
if WIFI_SSID:
now = time.ticks_ms()
if time.ticks_diff(now, next_wlan_check) >= 0:
next_wlan_check = time.ticks_add(now, RECONNECT_INTERVAL_MS)
if not wlan.isconnected():
print("WLAN getrennt, versuche erneut zu verbinden...")
try:
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
except Exception as e:
print("Reconnect-Fehler:", e)
elif api is None:
# (Wieder-)Verbunden, aber API lief noch nicht (z.B.
# weil sie beim Boot mangels WLAN nicht starten konnte)
try:
api = ApiServer(eyes, mouth_ref=mouth, heat_ring_ref=heat_ring, port=80)
print("API laeuft auf http://%s/" % wlan.ifconfig()[0])
except Exception as e:
print("API-Server konnte nicht gestartet werden:", e)
time.sleep_ms(12)