diff --git a/CLAUDE.md b/CLAUDE.md index 94f8a3a..532d7c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,6 +61,7 @@ Edit `config.json` to configure detectors: | Memory | `detectors/memory.py` | — | | Service | `detectors/service.py` | `SERVICES` (comma-separated process names) | | Network | `detectors/network.py` | `HOSTS` (comma-separated hostnames/IPs) | +| Docker | `detectors/docker.py` | `CONTAINERS` (optional, monitors all if empty) | All detectors support: `AGGREGATOR_URL`, `CHECK_INTERVAL`, `THRESHOLD_WARNING`, `THRESHOLD_CRITICAL` @@ -70,7 +71,8 @@ All detectors support: `AGGREGATOR_URL`, `CHECK_INTERVAL`, `THRESHOLD_WARNING`, |----------|--------|-------------| | `/event` | POST | Register event: `{"id": "name", "priority": 1-4, "message": "optional", "ttl": seconds}` | | `/clear` | POST | Clear event: `{"id": "name"}` | -| `/sleep` | POST | Enter sleep mode (for Home Assistant) | +| `/notify` | POST | Simple notification: `{"message": "text", "duration": 5}` | +| `/sleep` | POST | Enter sleep mode | | `/wake` | POST | Exit sleep mode | | `/status` | GET | Current state JSON | | `/events` | GET | List active events | @@ -93,7 +95,7 @@ The optimal state cycles through emotes with paired animations every 5 minutes: | Emote | Animation | Vibe | |-------|-----------|------| | `( ^_^)` | breathing | calm | -| `( ᵔᴥᵔ)` | floating | dreamy | +| `( ˙▿˙)` | floating | content | | `(◕‿◕)` | bouncing | cheerful | | `( ・ω・)` | swaying | curious | | `( ˘▽˘)` | breathing | cozy | @@ -104,6 +106,43 @@ Additional states: - **Connection lost**: `( ?.?)` gray, searching animation - **Sleep mode**: `( -_-)zzZ` dim, very slow breathing +## Home Assistant Integration + +Add REST commands to `configuration.yaml`: + +```yaml +rest_command: + kao_notify: + url: "http://kao-host:5100/notify" + method: POST + content_type: "application/json" + payload: '{"message": "{{ message }}", "duration": {{ duration | default(5) }}}' + + kao_sleep: + url: "http://kao-host:5100/sleep" + method: POST + + kao_wake: + url: "http://kao-host:5100/wake" + method: POST +``` + +Use in automations: + +```yaml +# Doorbell notification +- service: rest_command.kao_notify + data: + message: "Someone at the door" + duration: 10 + +# Bedtime routine +- service: rest_command.kao_sleep + +# Morning routine +- service: rest_command.kao_wake +``` + ## File Structure ``` @@ -116,7 +155,8 @@ Additional states: │ ├── cpu.py │ ├── memory.py │ ├── service.py -│ └── network.py +│ ├── network.py +│ └── docker.py ├── requirements.txt └── SPEC.md # Original project specification ``` diff --git a/README.md b/README.md index 4576a78..81ca12c 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,14 @@ A minimalist system status monitor that uses ASCII emotes to display server heal Turn an old phone (with its OLED screen) into a glanceable ambient display for your home server. Instead of graphs and numbers, see a happy face `( ^_^)` when things are good, and a worried face `( o_o)` when they're not. -## TODO - -- Figure out way to update installation in /opt/kao when git updates. Maybe run a git pull on service startup? -- Think about ways of implementing noises. - ## Features - **OLED-optimized** — Pure black background, saves battery - **Glanceable** — Know your server's status from across the room - **Extensible** — Add custom detectors for any metric - **Personality** — Rotating expressions, celebration animations, sleep mode -- **Home Assistant ready** — Webhook endpoints for automation +- **Sound effects** — Optional audio cues for state changes (tap to enable) +- **Home Assistant ready** — Webhook endpoints for notifications and automation ## Quick Start @@ -50,13 +46,14 @@ Open http://localhost:5100 on your phone (use Fully Kiosk Browser for best resul ## Built-in Detectors -| Detector | Monitors | -| -------------- | ----------------------------- | -| **disk_space** | Disk usage on all drives | -| **cpu** | CPU utilization | -| **memory** | RAM usage | -| **service** | Whether processes are running | -| **network** | Host reachability (ping) | +| Detector | Monitors | +| -------------- | ------------------------------------- | +| **disk_space** | Disk usage on all drives | +| **cpu** | CPU utilization | +| **memory** | RAM usage | +| **service** | Whether processes are running | +| **network** | Host reachability (ping) | +| **docker** | Container health and restart loops | ## Configuration @@ -104,57 +101,83 @@ curl -X POST http://localhost:5100/clear \ ## Home Assistant Integration -Add webhook commands to your Home Assistant config: +Add REST commands to your `configuration.yaml`: ```yaml rest_command: - sentry_sleep: + kao_notify: + url: "http://YOUR_SERVER:5100/notify" + method: POST + content_type: "application/json" + payload: '{"message": "{{ message }}", "duration": {{ duration | default(5) }}}' + + kao_sleep: url: "http://YOUR_SERVER:5100/sleep" method: POST - sentry_wake: + + kao_wake: url: "http://YOUR_SERVER:5100/wake" method: POST ``` -Trigger from automations: +Use in automations: ```yaml automation: - - alias: "Sentry Sleep at Bedtime" + - alias: "Doorbell Notification" + trigger: + platform: state + entity_id: binary_sensor.doorbell + to: "on" + action: + service: rest_command.kao_notify + data: + message: "Someone at the door" + duration: 10 + + - alias: "Kao Sleep at Bedtime" trigger: platform: time at: "23:00:00" action: - service: rest_command.sentry_sleep + service: rest_command.kao_sleep - - alias: "Sentry Wake in Morning" + - alias: "Kao Wake in Morning" trigger: platform: time at: "07:00:00" action: - service: rest_command.sentry_wake + service: rest_command.kao_wake ``` ## API Reference -| Endpoint | Method | Description | -| --------- | ------ | ---------------------- | -| `/` | GET | Web UI | -| `/status` | GET | Current state as JSON | -| `/events` | GET | List all active events | -| `/event` | POST | Register an event | -| `/clear` | POST | Clear an event by ID | -| `/sleep` | POST | Enter sleep mode | -| `/wake` | POST | Exit sleep mode | +| Endpoint | Method | Description | +| --------- | ------ | ------------------------------------------------ | +| `/` | GET | Web UI | +| `/status` | GET | Current state as JSON | +| `/events` | GET | List all active events | +| `/event` | POST | Register an event | +| `/clear` | POST | Clear an event by ID | +| `/notify` | POST | Simple notification `{"message": "", "duration": 5}` | +| `/sleep` | POST | Enter sleep mode | +| `/wake` | POST | Exit sleep mode | ## Personality The emote has personality! In optimal state it: - Rotates through happy faces every 5 minutes -- Occasionally winks `( -_^)` or blinks `( ᵕ.ᵕ)` +- Occasionally winks `( -_^)` or blinks `( ᵕ.ᵕ)` for a second or two - Celebrates `\(^o^)/` when recovering from warnings - Each face has its own animation (floating, bouncing, swaying) +- Reacts when tapped `( °o°)` and shows version info + +**Sound effects** (tap screen to enable, or use `?sound=on`): +- Warning: soft double-beep +- Critical: urgent descending tone +- Notify: gentle ping +- Recovery: happy ascending chirp ## License diff --git a/aggregator.py b/aggregator.py index e6e1e28..05ce60b 100644 --- a/aggregator.py +++ b/aggregator.py @@ -223,6 +223,34 @@ def clear_event(): return jsonify({"error": "Event not found"}), 404 +@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. + """ + data = request.get_json(force=True) if request.data else {} + message = data.get("message", "") + duration = int(data.get("duration", 5)) + + # Generate unique ID to avoid conflicts + event_id = f"ha_notify_{int(time.time() * 1000)}" + + event = { + "priority": 3, # Notify priority + "message": message, + "timestamp": time.time(), + "ttl": time.time() + duration, + } + + with events_lock: + active_events[event_id] = event + + state = write_status() + return jsonify({"status": "ok", "id": event_id, "current_state": state}), 200 + + @app.route("/sleep", methods=["POST"]) def sleep_mode(): """Enter sleep mode. For Home Assistant webhook."""