Enhance /notify with custom emote, color, animation, sound

- /notify now accepts optional: emote, color, animation, sound
- Backend passes custom properties to status response
- Frontend handles custom sounds (chime, alert, success, etc.)
- Added new sound effects: chime, alert, success
- Updated documentation with full notify options
- Added HA automation examples for doorbell and timer

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 21:50:32 -06:00
parent 942cdad5b8
commit 1ec67b4033
4 changed files with 152 additions and 14 deletions

View File

@@ -83,13 +83,18 @@ def get_current_state():
now = time.time()
top_event = None
with events_lock:
if not active_events:
priority = 4
events_list = []
else:
# Find highest priority (lowest number)
# Find highest priority (lowest number) and its event
priority = min(e["priority"] for e in active_events.values())
for eid, e in active_events.items():
if e["priority"] == priority:
top_event = e
break
events_list = [
{"id": eid, "priority": e["priority"], "message": e.get("message", "")}
for eid, e in active_events.items()
@@ -99,6 +104,18 @@ def get_current_state():
emote = config["emote"]
animation = config["animation"]
color = config["color"]
sound = None
# Check for custom display properties from top event
if top_event:
if "emote" in top_event:
emote = top_event["emote"]
if "color" in top_event:
color = top_event["color"]
if "animation" in top_event:
animation = top_event["animation"]
if "sound" in top_event:
sound = top_event["sound"]
# Check for recovery (was bad, now optimal)
if priority == 4 and previous_priority < 4:
@@ -106,8 +123,8 @@ def get_current_state():
previous_priority = priority
# Handle optimal state personality
if priority == 4:
# Handle optimal state personality (only if no custom overrides)
if priority == 4 and not top_event:
if now < celebrating_until:
# Celebration mode
emote, animation = CELEBRATION_EMOTE
@@ -127,7 +144,7 @@ def get_current_state():
emote = current_optimal_emote
animation = current_optimal_animation
return {
result = {
"current_state": config["name"].lower(),
"active_emote": emote,
"color": color,
@@ -137,6 +154,11 @@ def get_current_state():
"last_updated": datetime.now().isoformat(timespec="seconds"),
}
if sound:
result["sound"] = sound
return result
def write_status():
"""Write current state to status.json."""
@@ -226,9 +248,15 @@ def clear_event():
@app.route("/notify", methods=["POST"])
def notify():
"""
Simple notification endpoint for Home Assistant.
JSON: {"message": "text", "duration": 5}
Shows the Notify emote with message, auto-expires after duration.
Notification endpoint for Home Assistant.
JSON: {
"message": "text",
"duration": 5,
"emote": "( °o°)", # optional custom emote
"color": "#FF9900", # optional custom color
"animation": "popping", # optional: breathing, shaking, popping, celebrating, floating, bouncing, swaying
"sound": "chime" # optional: chime, alert, warning, critical, success, none
}
"""
data = request.get_json(force=True) if request.data else {}
message = data.get("message", "")
@@ -244,6 +272,16 @@ def notify():
"ttl": time.time() + duration,
}
# Optional custom display properties
if "emote" in data:
event["emote"] = data["emote"]
if "color" in data:
event["color"] = data["color"]
if "animation" in data:
event["animation"] = data["animation"]
if "sound" in data:
event["sound"] = data["sound"]
with events_lock:
active_events[event_id] = event