Compare commits

..
5 Commits
Author SHA1 Message Date
Till-Immer 7d9faca720 Dateien nach "openhab" hochladen 2026-09-22 13:54:07 +00:00
Till-Immer 08ff519216 Dateien nach "docs" hochladen 2026-09-22 13:53:39 +00:00
Till-Immer 73ea0441fb Dateien nach "app" hochladen 2026-09-22 13:53:11 +00:00
Till-Immer debc2a0bc8 Dateien nach "/" hochladen 2026-09-22 13:52:48 +00:00
Till-Immer 17b034e8f6 openhab-ai-bridge-v2.0-complete.zip gelöscht 2026-09-22 13:51:54 +00:00
11 changed files with 791 additions and 0 deletions

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
# Generate token: openssl rand -hex 32
API_TOKEN=CHANGE_ME_LONG_RANDOM_TOKEN
HOST=0.0.0.0
PORT=8090
# MQTT – same host/port as your openHAB Mosquitto
MQTT_HOST=192.168.178.60
MQTT_PORT=1884
MQTT_USERNAME=
MQTT_PASSWORD=
MQTT_TLS=false
MQTT_PREFIX=openhab/ai_bridge
DB_PATH=/data/bridge.db
MAX_HISTORY_MESSAGES=16
SESSION_TTL_SECONDS=7200
MAX_SESSIONS=20
LOG_LEVEL=INFO
# Pi speaker (ALSA)
ALSA_PLAYBACK_DEVICE=default
AUDIO_GID=29
# Free Edge TTS (German)
TTS_VOICE=de-DE-ConradNeural
TTS_RATE=+0%
TTS_VOLUME=+0%
+14
View File
@@ -0,0 +1,14 @@
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1
RUN apt-get update && apt-get install -y --no-install-recommends \
alsa-utils libasound2 ffmpeg libatomic1 \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin bridge
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app ./app
RUN mkdir -p /data /data/audio && chown -R bridge:bridge /app /data
USER bridge
EXPOSE 8090
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8090"]
+62
View File
@@ -0,0 +1,62 @@
# openHAB AI Bridge – Simple (Text + Claude + Edge TTS)
Kostenlose KI-Anbindung über **Ask Claude** (iOS Shortcut), ohne API-Key.
## Ablauf
```
openHAB Textfeld
→ Bridge (Session + Kontext in SQLite)
→ MQTT notify
→ openHAB Cloud Push ans iPhone
→ Shortcut: Ask Claude
→ Antwort zurück an Bridge
→ Edge TTS → Lautsprecher am Pi
```
Rückfragen nutzen denselben Session-Kontext (Standard: 2 Stunden).
## Schnellstart (Raspberry Pi)
```bash
cd ~/openhab-ai-bridge-simple
cp .env.example .env
openssl rand -hex 32 # → in .env als API_TOKEN eintragen
getent group audio | cut -d: -f3 # → AUDIO_GID setzen
docker compose build
docker compose up -d
curl http://127.0.0.1:8090/health
```
## openHAB
1. Thing + Items aus `openhab/` übernehmen (Broker-UID anpassen).
2. JS-Regel aus `openhab/ai_bridge.rules.js` (Token + IP eintragen).
3. Regel „AI Bridge Notify → Push“ sendet die Cloud-Notification.
## iOS Shortcut
1. Trigger: openHAB-Notification mit Text `KI_DIALOG`
2. GET `http://PI:8090/api/v1/active` (Header: `Authorization: Bearer TOKEN`)
3. GET `/api/v1/sessions/{session_id}/context`
4. Prompt aus Messages bauen → **Ask Claude**
5. POST `/api/v1/sessions/{session_id}/answer` `{"content":"..."}`
6. POST `/api/v1/audio/speak` `{"text":"..."}` (Pi spricht)
## API (alle außer /health brauchen Bearer-Token)
| Methode | Pfad | Zweck |
|---------|------|--------|
| POST | /api/v1/dialog | Frage aus openHAB |
| GET | /api/v1/active | Aktive Session |
| GET | /api/v1/sessions/{id}/context | Gesprächsverlauf |
| POST | /api/v1/sessions/{id}/answer | Claude-Antwort speichern |
| POST | /api/v1/audio/speak | Edge TTS abspielen |
| POST | /api/v1/sessions/{id}/end | Session beenden |
## Sicherheit
- Nur LAN, Port 8090 nicht nach außen freigeben
- Langer zufälliger API_TOKEN
- Docker: non-root, read-only root, dropped capabilities
+498
View File
@@ -0,0 +1,498 @@
"""
openHAB AI Bridge (simple) – text dialog + session context + Edge TTS.
Flow:
openHAB text → POST /api/v1/dialog → SQLite session
→ MQTT notify → openHAB Cloud push → iOS Shortcut
→ Ask Claude → POST /api/v1/sessions/{id}/answer
→ POST /api/v1/audio/speak → Pi speaker (Edge TTS)
"""
import os
import json
import uuid
import logging
import sqlite3
import subprocess
import time
from pathlib import Path
from contextlib import contextmanager
from datetime import datetime, timezone
from fastapi import FastAPI, HTTPException, Header, Depends
from pydantic import BaseModel, Field
import paho.mqtt.client as mqtt
logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO").upper(),
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
log = logging.getLogger("openhab-ai-bridge")
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", "8090"))
API_TOKEN = os.environ["API_TOKEN"]
DB_PATH = os.getenv("DB_PATH", "/data/bridge.db")
MAX_HISTORY = int(os.getenv("MAX_HISTORY_MESSAGES", "16"))
SESSION_TTL = int(os.getenv("SESSION_TTL_SECONDS", "7200"))
MAX_SESSIONS = int(os.getenv("MAX_SESSIONS", "20"))
MQTT_HOST = os.getenv("MQTT_HOST", "192.168.178.60")
MQTT_PORT = int(os.getenv("MQTT_PORT", "1884"))
MQTT_PREFIX = os.getenv("MQTT_PREFIX", "openhab/ai_bridge").strip("/")
MQTT_USER = os.getenv("MQTT_USERNAME", "")
MQTT_PASS = os.getenv("MQTT_PASSWORD", "")
MQTT_TLS = os.getenv("MQTT_TLS", "false").lower() == "true"
ALSA_PLAYBACK = os.getenv("ALSA_PLAYBACK_DEVICE", "default")
TTS_VOICE = os.getenv("TTS_VOICE", "de-DE-ConradNeural")
TTS_RATE = os.getenv("TTS_RATE", "+0%")
TTS_VOLUME = os.getenv("TTS_VOLUME", "+0%")
Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
app = FastAPI(title="openHAB AI Bridge", version="3.0.0-simple")
def now() -> str:
return datetime.now(timezone.utc).isoformat()
@contextmanager
def db():
c = sqlite3.connect(DB_PATH, timeout=10)
c.row_factory = sqlite3.Row
try:
c.execute("PRAGMA journal_mode=WAL")
c.execute("PRAGMA busy_timeout=5000")
yield c
c.commit()
finally:
c.close()
def init_db():
with db() as c:
c.execute(
"""CREATE TABLE IF NOT EXISTS sessions(
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
prompt TEXT NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
last_response TEXT NOT NULL DEFAULT '')"""
)
c.execute(
"""CREATE TABLE IF NOT EXISTS messages(
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL)"""
)
c.execute(
"CREATE INDEX IF NOT EXISTS idx_msg ON messages(session_id, id)"
)
def mqtt_pub(topic: str, payload: str, retain: bool = False):
cl = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"openhab-ai-bridge-{uuid.uuid4().hex[:8]}",
)
try:
if MQTT_USER:
cl.username_pw_set(MQTT_USER, MQTT_PASS)
if MQTT_TLS:
cl.tls_set()
cl.connect(MQTT_HOST, MQTT_PORT, 10)
info = cl.publish(topic, payload, qos=1, retain=retain)
info.wait_for_publish(5)
cl.disconnect()
except Exception as e:
log.error("MQTT publish failed topic=%s error=%s", topic, e)
def auth(authorization: str | None = Header(default=None)):
if authorization != f"Bearer {API_TOKEN}":
raise HTTPException(401, "Unauthorized")
def cleanup():
cutoff = time.time() - SESSION_TTL
with db() as c:
rows = c.execute(
"SELECT id, updated_at FROM sessions WHERE status NOT IN ('ended','expired')"
).fetchall()
for r in rows:
try:
if datetime.fromisoformat(r["updated_at"]).timestamp() < cutoff:
c.execute(
"UPDATE sessions SET status='expired', updated_at=? WHERE id=?",
(now(), r["id"]),
)
except Exception:
pass
c.execute(
"""DELETE FROM messages WHERE session_id IN
(SELECT id FROM sessions WHERE status IN ('ended','expired')
AND updated_at < datetime('now','-7 days'))"""
)
c.execute(
"""DELETE FROM sessions WHERE status IN ('ended','expired')
AND updated_at < datetime('now','-7 days')"""
)
def session_dict(r) -> dict:
return {
"session_id": r["id"],
"title": r["title"],
"prompt": r["prompt"],
"status": r["status"],
"created_at": r["created_at"],
"updated_at": r["updated_at"],
"last_response": r["last_response"],
}
def publish_active_session(sid: str | None):
mqtt_pub(f"{MQTT_PREFIX}/active_session", sid or "", True)
class SessionCreate(BaseModel):
title: str = Field(default="KI-Anfrage", max_length=200)
prompt: str = Field(min_length=1, max_length=8000)
notify_text: str | None = Field(default=None, max_length=1000)
class Answer(BaseModel):
content: str = Field(min_length=1, max_length=12000)
class ContinueReq(BaseModel):
content: str = Field(min_length=1, max_length=8000)
class DialogReq(BaseModel):
text: str = Field(min_length=1, max_length=8000)
title: str = Field(default="openHAB Dialog", max_length=200)
notify_text: str | None = Field(default="KI_DIALOG", max_length=1000)
class SpeakReq(BaseModel):
text: str = Field(min_length=1, max_length=12000)
voice: str | None = Field(default=None, max_length=100)
@app.on_event("startup")
def startup():
init_db()
mqtt_pub(f"{MQTT_PREFIX}/availability", "ONLINE", True)
mqtt_pub(f"{MQTT_PREFIX}/status", "IDLE", True)
log.info(
"started port=%s mqtt=%s:%s prefix=%s",
PORT,
MQTT_HOST,
MQTT_PORT,
MQTT_PREFIX,
)
@app.on_event("shutdown")
def shutdown():
mqtt_pub(f"{MQTT_PREFIX}/availability", "OFFLINE", True)
@app.get("/health")
def health():
return {"ok": True, "service": "openhab-ai-bridge", "version": "3.0.0-simple"}
@app.get("/api/v1/pending")
def pending(_: None = Depends(auth)):
cleanup()
with db() as c:
r = c.execute(
"SELECT * FROM sessions WHERE status='pending' ORDER BY created_at LIMIT 1"
).fetchone()
return {"pending": bool(r), **(session_dict(r) if r else {})}
@app.get("/api/v1/active")
def active(_: None = Depends(auth)):
cleanup()
with db() as c:
r = c.execute(
"""SELECT * FROM sessions
WHERE status IN ('pending','processing','active')
ORDER BY updated_at DESC LIMIT 1"""
).fetchone()
return {"active": bool(r), **(session_dict(r) if r else {})}
def _create_session(title: str, prompt: str, notify_text: str | None, source: str) -> str:
cleanup()
with db() as c:
n = c.execute(
"SELECT COUNT(*) FROM sessions WHERE status IN ('pending','processing','active')"
).fetchone()[0]
if n >= MAX_SESSIONS:
raise HTTPException(429, "Too many active sessions")
sid = str(uuid.uuid4())
ts = now()
c.execute(
"INSERT INTO sessions VALUES(?,?,?,?,?,?,?)",
(sid, title, prompt, "pending", ts, ts, ""),
)
c.execute(
"INSERT INTO messages(session_id, role, content, created_at) VALUES(?,?,?,?)",
(sid, "user", prompt, ts),
)
mqtt_pub(f"{MQTT_PREFIX}/status", "PENDING", True)
publish_active_session(sid)
mqtt_pub(
f"{MQTT_PREFIX}/session/new",
json.dumps(
{
"session_id": sid,
"title": title,
"prompt": prompt,
"notify_text": notify_text,
"source": source,
},
ensure_ascii=False,
),
)
# Notify topic for openHAB → Cloud Push
mqtt_pub(
f"{MQTT_PREFIX}/notify",
json.dumps(
{
"session_id": sid,
"kind": "dialog",
"text": prompt[:200],
"notify_text": notify_text or "KI_DIALOG",
},
ensure_ascii=False,
),
)
log.info("session created id=%s source=%s title=%r", sid, source, title)
return sid
@app.post("/api/v1/sessions")
def create(req: SessionCreate, _: None = Depends(auth)):
sid = _create_session(req.title, req.prompt, req.notify_text, "api")
return {
"session_id": sid,
"status": "pending",
"title": req.title,
"prompt": req.prompt,
}
@app.get("/api/v1/sessions/{sid}")
def get_session(sid: str, _: None = Depends(auth)):
with db() as c:
r = c.execute("SELECT * FROM sessions WHERE id=?", (sid,)).fetchone()
if not r:
raise HTTPException(404, "Session not found")
return session_dict(r)
@app.get("/api/v1/sessions/{sid}/context")
def context(sid: str, _: None = Depends(auth)):
with db() as c:
s = c.execute("SELECT * FROM sessions WHERE id=?", (sid,)).fetchone()
if not s:
raise HTTPException(404, "Session not found")
m = c.execute(
"SELECT role, content, created_at FROM messages "
"WHERE session_id=? ORDER BY id DESC LIMIT ?",
(sid, MAX_HISTORY),
).fetchall()
return {
"session_id": sid,
"title": s["title"],
"status": s["status"],
"messages": [dict(x) for x in reversed(m)],
}
@app.post("/api/v1/sessions/{sid}/answer")
def answer(sid: str, req: Answer, _: None = Depends(auth)):
with db() as c:
s = c.execute("SELECT id FROM sessions WHERE id=?", (sid,)).fetchone()
if not s:
raise HTTPException(404, "Session not found")
ts = now()
c.execute(
"UPDATE sessions SET status='active', updated_at=?, last_response=? WHERE id=?",
(ts, req.content, sid),
)
c.execute(
"INSERT INTO messages(session_id, role, content, created_at) VALUES(?,?,?,?)",
(sid, "assistant", req.content, ts),
)
mqtt_pub(f"{MQTT_PREFIX}/last_response", req.content, True)
mqtt_pub(f"{MQTT_PREFIX}/status", "ACTIVE", True)
publish_active_session(sid)
mqtt_pub(f"{MQTT_PREFIX}/session/{sid}/answer", req.content)
log.info("answer id=%s chars=%d", sid, len(req.content))
return {"ok": True, "session_id": sid, "status": "active"}
@app.post("/api/v1/sessions/{sid}/continue")
def cont(sid: str, req: ContinueReq, _: None = Depends(auth)):
with db() as c:
s = c.execute("SELECT status FROM sessions WHERE id=?", (sid,)).fetchone()
if not s:
raise HTTPException(404, "Session not found")
if s["status"] in ("ended", "expired"):
raise HTTPException(409, "Session ended")
ts = now()
c.execute(
"UPDATE sessions SET status='processing', updated_at=? WHERE id=?",
(ts, sid),
)
c.execute(
"INSERT INTO messages(session_id, role, content, created_at) VALUES(?,?,?,?)",
(sid, "user", req.content, ts),
)
mqtt_pub(f"{MQTT_PREFIX}/status", "PROCESSING", True)
publish_active_session(sid)
mqtt_pub(f"{MQTT_PREFIX}/session/{sid}/user", req.content)
mqtt_pub(
f"{MQTT_PREFIX}/notify",
json.dumps(
{
"session_id": sid,
"kind": "dialog",
"text": req.content[:200],
"notify_text": "KI_DIALOG",
},
ensure_ascii=False,
),
)
return {"ok": True, "session_id": sid, "status": "processing"}
@app.post("/api/v1/dialog")
def dialog(req: DialogReq, _: None = Depends(auth)):
"""
openHAB entry point.
Reuses active session if present, otherwise creates a new one.
Publishes MQTT notify so openHAB can trigger the Cloud push.
"""
cleanup()
with db() as c:
s = c.execute(
"""SELECT * FROM sessions
WHERE status IN ('active','processing','pending')
ORDER BY updated_at DESC LIMIT 1"""
).fetchone()
if s:
sid = s["id"]
cont(sid, ContinueReq(content=req.text))
mqtt_pub(
f"{MQTT_PREFIX}/dialog/input",
json.dumps({"session_id": sid, "text": req.text}, ensure_ascii=False),
)
return {"session_id": sid, "status": "processing", "reused": True}
sid = _create_session(req.title, req.text, req.notify_text, "openhab")
mqtt_pub(
f"{MQTT_PREFIX}/dialog/input",
json.dumps({"session_id": sid, "text": req.text}, ensure_ascii=False),
)
return {"session_id": sid, "status": "pending", "reused": False}
@app.post("/api/v1/sessions/{sid}/end")
def end(sid: str, _: None = Depends(auth)):
with db() as c:
if not c.execute("SELECT id FROM sessions WHERE id=?", (sid,)).fetchone():
raise HTTPException(404, "Session not found")
c.execute(
"UPDATE sessions SET status='ended', updated_at=? WHERE id=?",
(now(), sid),
)
mqtt_pub(f"{MQTT_PREFIX}/status", "IDLE", True)
publish_active_session("")
mqtt_pub(f"{MQTT_PREFIX}/session/{sid}/ended", "ENDED")
return {"ok": True, "session_id": sid, "status": "ended"}
@app.post("/api/v1/audio/speak")
async def speak(req: SpeakReq, _: None = Depends(auth)):
"""Edge TTS (free) → WAV → physical Pi speaker via ALSA."""
outdir = Path("/data/audio")
outdir.mkdir(parents=True, exist_ok=True)
ident = uuid.uuid4().hex
mp3 = outdir / f"{ident}.mp3"
wav = outdir / f"{ident}.wav"
voice = req.voice or TTS_VOICE
try:
import edge_tts
communicate = edge_tts.Communicate(
req.text, voice, rate=TTS_RATE, volume=TTS_VOLUME
)
await communicate.save(str(mp3))
p = subprocess.run(
[
"ffmpeg",
"-y",
"-loglevel",
"error",
"-i",
str(mp3),
"-ar",
"48000",
"-ac",
"2",
str(wav),
],
capture_output=True,
text=True,
timeout=60,
)
if p.returncode:
raise HTTPException(500, p.stderr[-1000:] or "TTS conversion failed")
p = subprocess.run(
["aplay", "-D", ALSA_PLAYBACK, str(wav)],
capture_output=True,
text=True,
timeout=180,
)
if p.returncode:
raise HTTPException(500, p.stderr[-1000:] or "ALSA playback failed")
return {"ok": True, "voice": voice, "speaker": "pi-alsa"}
finally:
for f in (mp3, wav):
try:
f.unlink()
except FileNotFoundError:
pass
@app.get("/api/v1/audio/devices")
def audio_devices(_: None = Depends(auth)):
out = {}
for k, cmd in {
"playback": ["aplay", "-l"],
"capture": ["arecord", "-l"],
}.items():
try:
p = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
out[k] = {
"returncode": p.returncode,
"stdout": p.stdout,
"stderr": p.stderr,
}
except Exception as e:
out[k] = {"error": str(e)}
return out
+33
View File
@@ -0,0 +1,33 @@
services:
openhab-ai-bridge:
build: .
container_name: openhab-ai-bridge
restart: unless-stopped
env_file: .env
ports:
- "8090:8090"
volumes:
- ./data:/data
devices:
- /dev/snd:/dev/snd
group_add:
- "${AUDIO_GID:-29}"
read_only: true
tmpfs:
- /tmp:size=32m,noexec,nosuid,nodev
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8090/health',timeout=2).read()",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
+78
View File
@@ -0,0 +1,78 @@
# Setup – Simple Bridge
## 1. Raspberry Pi
```bash
cd ~
# Projekt kopieren (oder aus dem ZIP)
cp -r openhab-ai-bridge-simple ~/
cd ~/openhab-ai-bridge-simple
cp .env.example .env
openssl rand -hex 32
nano .env # API_TOKEN eintragen, MQTT_HOST prüfen
```
Audio-Gruppe:
```bash
getent group audio | cut -d: -f3
# Wert in AUDIO_GID in .env
```
Start:
```bash
docker compose build && docker compose up -d
docker compose logs -f
curl http://127.0.0.1:8090/health
```
TTS-Test:
```bash
curl -X POST http://127.0.0.1:8090/api/v1/audio/speak \
-H "Authorization: Bearer DEIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text":"Hallo, das ist ein Test vom Raspberry Pi."}'
```
## 2. openHAB
### Thing
Datei `openhab/ai_bridge.things` nach `$OPENHAB_CONF/things/` kopieren
oder im Main UI manuell anlegen (Broker-UID: `mqtt:broker:72a3b87ef9`).
### Items
Datei `openhab/ai_bridge.items` nach `$OPENHAB_CONF/items/` kopieren
oder Items über die API/MCP anlegen und manuell mit den Channels verlinken.
### JS-Regel (Dialog → Bridge)
- Add-on **JavaScript Scripting** installieren
- Inhalt von `openhab/ai_bridge.rules.js` als Script anlegen
- `BRIDGE_URL` und `BRIDGE_TOKEN` anpassen
### Push-Regel
Wenn Item `AI_Bridge_Notify` sich ändert → openHAB Cloud Notification
mit Text z. B. `KI_DIALOG <session_id>` an dein iPhone senden.
Die Notification muss die Automation auf dem iPhone auslösen.
## 3. iOS Shortcut
1. Automation: „Wenn Benachrichtigung von openHAB empfangen“ und Text enthält `KI_DIALOG`
2. HTTP GET `http://PI-IP:8090/api/v1/active`
Header: `Authorization: Bearer TOKEN`
3. Session-ID aus der Antwort nehmen
4. HTTP GET `.../api/v1/sessions/{id}/context`
5. Messages zu einem Prompt zusammensetzen
6. Aktion **Ask Claude**
7. HTTP POST `.../api/v1/sessions/{id}/answer`
Body: `{"content":"<Claude-Antwort>"}`
8. HTTP POST `.../api/v1/audio/speak`
Body: `{"text":"<Claude-Antwort>"}`
Optional: Button „Gespräch beenden“ → POST `.../sessions/{id}/end`
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
Group gAI_Bridge "AI Bridge"
String AI_Bridge_Availability "AI Bridge Online [%s]" (gAI_Bridge) {channel="mqtt:topic:ai_bridge:availability"}
String AI_Bridge_Status "AI Bridge Status [%s]" (gAI_Bridge) {channel="mqtt:topic:ai_bridge:status"}
String AI_Bridge_LastResponse "KI Antwort [%s]" (gAI_Bridge) {channel="mqtt:topic:ai_bridge:last_response"}
String AI_Bridge_ActiveSession "Aktive Session [%s]" (gAI_Bridge) {channel="mqtt:topic:ai_bridge:active_session"}
String AI_Bridge_Notify "AI Notify [%s]" (gAI_Bridge) {channel="mqtt:topic:ai_bridge:notify"}
String AI_Bridge_DialogInput "Dialog Input [%s]" (gAI_Bridge) {channel="mqtt:topic:ai_bridge:dialog_input"}
// Textfeld in der UI – Befehl löst die JS-Regel aus
String AI_Bridge_DialogCommand "KI fragen" (gAI_Bridge)
+40
View File
@@ -0,0 +1,40 @@
// openHAB JS Scripting – Dialog an die Bridge senden
// JavaScript Scripting Add-on muss installiert/aktiviert sein.
//
// Token und URL anpassen!
const { rules, triggers, actions } = require('openhab');
const BRIDGE_URL = 'http://192.168.178.60:8090/api/v1/dialog';
const BRIDGE_TOKEN = 'REPLACE_WITH_BRIDGE_TOKEN';
rules.JSRule({
name: 'AI Bridge - Dialog senden',
description: 'Sendet Text aus AI_Bridge_DialogCommand an die Bridge und startet den Claude-Flow',
tags: ['AI', 'MCP'],
triggers: [triggers.ItemCommandTrigger('AI_Bridge_DialogCommand')],
execute: (event) => {
const text = String(event.receivedCommand || '').trim();
if (!text) return;
try {
actions.HTTP.sendHttpPostRequest(
BRIDGE_URL,
'application/json',
JSON.stringify({
text: text,
title: 'openHAB Dialog',
notify_text: 'KI_DIALOG'
}),
{
'Authorization': 'Bearer ' + BRIDGE_TOKEN,
'Content-Type': 'application/json'
},
8000
);
console.info('AI Bridge dialog sent: ' + text.substring(0, 80));
} catch (e) {
console.error('AI Bridge dialog failed: ' + e);
}
}
});
+23
View File
@@ -0,0 +1,23 @@
// Broker-UID an deine Installation anpassen:
// Dein Broker: mqtt:broker:72a3b87ef9
Thing mqtt:topic:ai_bridge "AI Bridge" (mqtt:broker:72a3b87ef9) {
Channels:
Type string : availability "Availability" [
stateTopic="openhab/ai_bridge/availability"
]
Type string : status "Status" [
stateTopic="openhab/ai_bridge/status"
]
Type string : last_response "Last response" [
stateTopic="openhab/ai_bridge/last_response"
]
Type string : active_session "Active session" [
stateTopic="openhab/ai_bridge/active_session"
]
Type string : notify "Notify" [
stateTopic="openhab/ai_bridge/notify"
]
Type string : dialog_input "Dialog input" [
stateTopic="openhab/ai_bridge/dialog/input"
]
}
+4
View File
@@ -0,0 +1,4 @@
fastapi==0.116.1
uvicorn[standard]==0.35.0
paho-mqtt==2.1.0
edge-tts==7.2.7