Bump to v1.5.0: deduplicate detectors, fix aggregator bugs, fix blocking I/O
- Extract shared send_event/clear_event into detectors/base.py, removing ~150 lines of duplication across all 6 detectors - Fix default aggregator URL from port 5000 to 5100 in all detectors - Standardize cpu.py and memory.py to use active_alerts set pattern - Fix immediate emote rotation on startup (last_emote_change = time.time()) - Extract magic numbers to named constants in aggregator - Protect write_status() with try/except OSError - Fix notify event ID collision with monotonic counter - Replace blocking stream_output() with background daemon threads in kao.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,9 @@ ROOT_DIR = Path(__file__).parent
|
||||
STATUS_FILE = Path(__file__).parent / "status.json"
|
||||
DEFAULT_NOTIFY_TTL = 10 # Default TTL for Priority 3 (Notify) events
|
||||
CELEBRATION_DURATION = 5 # Seconds to show celebration after recovery
|
||||
EMOTE_ROTATION_INTERVAL = 300 # Seconds between emote rotations
|
||||
IDLE_EXPRESSION_CHANCE = 0.15 # Chance of a brief blink/wink on rotation
|
||||
DEFAULT_NOTIFY_DURATION = 5 # Default duration for /notify events
|
||||
|
||||
# Emote variations with paired animations
|
||||
OPTIMAL_EMOTES = [
|
||||
@@ -53,10 +56,13 @@ celebrating_until = 0
|
||||
blinking_until = 0
|
||||
blink_emote = None
|
||||
blink_animation = None
|
||||
last_emote_change = 0
|
||||
last_emote_change = time.time()
|
||||
current_optimal_emote = OPTIMAL_EMOTES[0][0]
|
||||
current_optimal_animation = OPTIMAL_EMOTES[0][1]
|
||||
|
||||
# Notify counter for unique IDs
|
||||
_notify_counter = 0
|
||||
|
||||
# Sleep mode
|
||||
is_sleeping = False
|
||||
SLEEP_EMOTE = "( -_-)zzZ"
|
||||
@@ -134,11 +140,11 @@ def get_current_state():
|
||||
animation = blink_animation
|
||||
else:
|
||||
# Rotate optimal emotes every 5 minutes
|
||||
if now - last_emote_change > 300:
|
||||
if now - last_emote_change > EMOTE_ROTATION_INTERVAL:
|
||||
last_emote_change = now
|
||||
current_optimal_emote, current_optimal_animation = random.choice(OPTIMAL_EMOTES)
|
||||
# 15% chance of a brief blink/wink
|
||||
if random.random() < 0.15:
|
||||
# Brief blink/wink chance on rotation
|
||||
if random.random() < IDLE_EXPRESSION_CHANCE:
|
||||
blink_emote, blink_animation = random.choice(IDLE_EMOTES)
|
||||
blinking_until = now + random.uniform(1, 2)
|
||||
emote = current_optimal_emote
|
||||
@@ -163,8 +169,11 @@ def get_current_state():
|
||||
def write_status():
|
||||
"""Write current state to status.json."""
|
||||
state = get_current_state()
|
||||
with open(STATUS_FILE, "w") as f:
|
||||
json.dump(state, f, indent="\t")
|
||||
try:
|
||||
with open(STATUS_FILE, "w") as f:
|
||||
json.dump(state, f, indent="\t")
|
||||
except OSError as e:
|
||||
print(f"[ERROR] Failed to write status file: {e}")
|
||||
return state
|
||||
|
||||
|
||||
@@ -272,12 +281,14 @@ def notify():
|
||||
"sound": "chime" # optional: chime, alert, warning, critical, success, none
|
||||
}
|
||||
"""
|
||||
global _notify_counter
|
||||
data = request.get_json(force=True) if request.data else {}
|
||||
message = data.get("message", "")
|
||||
duration = int(data.get("duration", 5))
|
||||
duration = int(data.get("duration", DEFAULT_NOTIFY_DURATION))
|
||||
|
||||
# Generate unique ID to avoid conflicts
|
||||
event_id = f"ha_notify_{int(time.time() * 1000)}"
|
||||
_notify_counter += 1
|
||||
event_id = f"notify_{int(time.time())}_{_notify_counter}"
|
||||
|
||||
event = {
|
||||
"priority": 3, # Notify priority
|
||||
|
||||
Reference in New Issue
Block a user