-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontrol_buntich.py
More file actions
161 lines (136 loc) · 5.9 KB
/
control_buntich.py
File metadata and controls
161 lines (136 loc) · 5.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# two_color_control.py
import re
import json
import requests
import logging
from typing import Tuple
import ollama
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Konfiguration
OLLAMA_HOST = "http://host.docker.internal:11434" # oder "http://localhost:11434" / env var
MODEL = "qwen3:8b"
RUND_BUNT_URL = "http://rundbunt/cgi" # Ziel für POST
# Initialisiere Ollama-Client (nutze diese Verbindung für alle Aufrufe)
client = ollama.Client(host=OLLAMA_HOST)
def extract_first_json(text: str) -> dict:
"""
Extrahiert das erste JSON-Objekt aus 'text' (alles zwischen erstem '{' und letztem '}')
und gibt das geparste dict zurück.
Wir nutzen einen einfachen regex-Fallback, da Modelle oft Text/Markdown drumherum liefern.
"""
match = re.search(r"\{.*\}", text, re.DOTALL)
if not match:
raise ValueError("Kein JSON-Objekt im Modell-Output gefunden.")
json_text = match.group(0)
try:
data = json.loads(json_text)
except json.JSONDecodeError as e:
# Fehler beim Parsen: hilfreichste Fehlermeldung loggen
logger.error("JSON-Parsing-Fehler: %s\nJSON-Text:\n%s", e, json_text)
raise
return data
import logging
def extract_last_json(text: str) -> dict:
"""
Extrahiert das letzte JSON-Objekt aus 'text' (alles zwischen '{' und '}')
und gibt es als dict zurück.
Nützlich, wenn das Modell mehrere JSON-Schnipsel schreibt,
aber der letzte der gültige ist.
"""
matches = re.findall(r"\{.*?\}", text, re.DOTALL)
if not matches:
raise ValueError("Kein JSON-Objekt im Modell-Output gefunden.")
json_text = matches[-1] # letzter Block
try:
return json.loads(json_text)
except json.JSONDecodeError as e:
logger.error("JSON-Parsing-Fehler: %s\nJSON-Text:\n%s", e, json_text)
raise
def normalize_hex_color(value: str) -> str:
"""
Normalisiert Farbwerte auf 6-stellige hex ohne '#', lowercase.
Akzeptiert z.B. '#fff', 'FFF', '00FF00', '0x00ff00' (teilweise).
Gibt ValueError bei ungültigem Input.
"""
if not isinstance(value, str):
raise ValueError("Farbwert muss ein String sein.")
v = value.strip().lower()
# Entferne übliche Prefixe
if v.startswith("0x"):
v = v[2:]
if v.startswith("#"):
v = v[1:]
# Kurzform #fff -> fff -> ffffff
if len(v) == 3:
v = "".join([c*2 for c in v])
# Falls zu lang/kurz: Fehler
if len(v) != 6 or not re.fullmatch(r"[0-9a-f]{6}", v):
raise ValueError(f"Ungültiger Hex-Farbwert: '{value}' -> '{v}'")
return v
def send_colors_to_rundbunt(color1: str, color2: str, gradient: int) -> requests.Response:
"""
Baut das Formular und sendet POST an RUND_BUNT_URL.
Erwartet color1/color2 als 6-stellige hex (ohne '#') und gradient als int (0/1).
"""
payload = {
"TwoColor_Color1": color1,
"TwoColor_Color2": color2,
"TwoColor_Gradient": str(int(bool(gradient))),
}
logger.info("Sende POST an %s mit payload=%s", RUND_BUNT_URL, payload)
resp = requests.post(RUND_BUNT_URL, data=payload, timeout=10)
resp.raise_for_status()
return resp
def main():
# Prompt: Fordere das Modell auf, JSON zurückzugeben, aber erwarte evtl. extra Text -> wir parsen robust
messages = [
{"role": "system", "content": (
"Du bist ein Assistent. Antworte mit einem JSON-Objekt. "
"Du darfst zusätzlichen Text schreiben, aber das JSON muss vollständig und korrekt sein.")},
{"role": "user", "content": (
"Gib mir bitte die Farben für eine TwoColor-Lampe als JSON zurück. "
"Felder: 'top' (Farbe oben), 'bottom' (Farbe unten), optional 'gradient' (0 oder 1). "
"Beispiel-JSON: {\"top\":\"#000000\",\"bottom\":\"#ffff00\",\"gradient\":1}. "
"Zusätzliche Hinweise kannst du außerhalb des JSON schreiben."
"Die Farben setzen sich so wie bei HTML zusammen. "
"Setze die Farbe oben auf gelb, die Farbe unten auf pink, mache aber keinen Gradienten."
)},
]
logger.info("Sende Anfrage an Modell %s...", MODEL)
response = client.chat(model=MODEL, messages=messages)
# Extrahiere die eigentliche Text-Antwort (modellabhängig)
content = response["message"]["content"]
logger.info("Roh-Antwort vom Modell:\n%s", content)
# Robustes Parsen des ersten JSON-Objekts
try:
data = extract_last_json(content)
except Exception as e:
logger.exception("Konnte JSON nicht extrahieren/parsieren: %s", e)
return
# Ziehe die Werte raus, mit Fallbacks / Validierung
try:
raw_top = data.get("top") or data.get("topColor") or data.get("top_color") or data.get("TwoColor_Color1")
raw_bottom = data.get("bottom") or data.get("bottomColor") or data.get("bottom_color") or data.get("TwoColor_Color2")
raw_gradient = data.get("gradient", data.get("TwoColor_Gradient", 1))
if raw_top is None or raw_bottom is None:
raise KeyError("Benötigte Felder 'top' und 'bottom' fehlen im JSON.")
top = normalize_hex_color(raw_top)
bottom = normalize_hex_color(raw_bottom)
gradient = int(raw_gradient)
if gradient not in (0, 1):
gradient = 1 # Default-Fallback
except Exception as e:
logger.exception("Fehler bei Validierung der Farbwerte: %s", e)
return
# POST an das Gerät
try:
resp = send_colors_to_rundbunt(top, bottom, gradient)
logger.info("POST erfolgreich. Antwortcode: %s, Text: %s", resp.status_code, resp.text)
print("Erfolgreich gesendet:", {"TwoColor_Color1": top, "TwoColor_Color2": bottom, "TwoColor_Gradient": gradient})
except requests.HTTPError as e:
logger.exception("HTTP-Fehler beim Senden: %s", e)
except requests.RequestException as e:
logger.exception("Netzwerkfehler beim Senden: %s", e)
if __name__ == "__main__":
main()