feat: Music Assistant, Claude primary LLM, model tag in chat, setup.sh rewrite
- Deploy Music Assistant on Pi (10.0.0.199:8095) with host networking for Chromecast mDNS discovery, Spotify + SMB library support - Switch primary LLM from Ollama to Claude Sonnet 4 (Anthropic API), local models remain as fallback - Add model info tag under each assistant message in dashboard chat, persisted in conversation JSON - Rewrite homeai-agent/setup.sh: loads .env, injects API keys into plists, symlinks plists to ~/Library/LaunchAgents/, smoke tests services - Update install_service() in common.sh to use symlinks instead of copies - Open UFW ports on Pi for Music Assistant (8095, 8097, 8927) - Add ANTHROPIC_API_KEY to openclaw + bridge launchd plists Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
90
homeai-agent/reminder-daemon.py
Executable file
90
homeai-agent/reminder-daemon.py
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
HomeAI Reminder Daemon — checks ~/homeai-data/reminders.json every 60s
|
||||
and fires TTS via POST http://localhost:8081/api/tts when reminders are due.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
|
||||
REMINDERS_FILE = os.path.expanduser("~/homeai-data/reminders.json")
|
||||
TTS_URL = "http://localhost:8081/api/tts"
|
||||
CHECK_INTERVAL = 60 # seconds
|
||||
|
||||
|
||||
def load_reminders():
|
||||
try:
|
||||
with open(REMINDERS_FILE) as f:
|
||||
return json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return {"reminders": []}
|
||||
|
||||
|
||||
def save_reminders(data):
|
||||
with open(REMINDERS_FILE, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
|
||||
def fire_tts(message):
|
||||
"""Speak reminder via the OpenClaw bridge TTS endpoint."""
|
||||
try:
|
||||
payload = json.dumps({"text": f"Reminder: {message}"}).encode()
|
||||
req = urllib.request.Request(
|
||||
TTS_URL,
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST"
|
||||
)
|
||||
urllib.request.urlopen(req, timeout=30)
|
||||
print(f"[{datetime.now().isoformat()}] TTS fired: {message}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now().isoformat()}] TTS error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def check_reminders():
|
||||
data = load_reminders()
|
||||
now = datetime.now()
|
||||
changed = False
|
||||
|
||||
for r in data.get("reminders", []):
|
||||
if r.get("fired"):
|
||||
continue
|
||||
|
||||
try:
|
||||
due = datetime.fromisoformat(r["due_at"])
|
||||
except (KeyError, ValueError):
|
||||
continue
|
||||
|
||||
if now >= due:
|
||||
print(f"[{now.isoformat()}] Reminder due: {r.get('message', '?')}")
|
||||
fire_tts(r["message"])
|
||||
r["fired"] = True
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
# Clean up fired reminders older than 24h
|
||||
cutoff = (now.timestamp() - 86400) * 1000
|
||||
data["reminders"] = [
|
||||
r for r in data["reminders"]
|
||||
if not r.get("fired") or int(r.get("id", "0")) > cutoff
|
||||
]
|
||||
save_reminders(data)
|
||||
|
||||
|
||||
def main():
|
||||
print(f"[{datetime.now().isoformat()}] Reminder daemon started (check every {CHECK_INTERVAL}s)")
|
||||
while True:
|
||||
try:
|
||||
check_reminders()
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now().isoformat()}] Error: {e}")
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user