Flask static folder wasn't serving .yaml files automatically. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Kao
A minimalist system status monitor that uses ASCII emotes to display server health on an old phone.
Why?
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.
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
- Sound effects — Optional audio cues for state changes (tap to enable)
- Home Assistant ready — Webhook endpoints for notifications and automation
Quick Start
# Clone and setup
git clone https://github.com/yourusername/kao.git
cd kao
python -m venv venv
source venv/bin/activate # Windows: .\venv\Scripts\activate
pip install -r requirements.txt
# Run everything
python kao.py
Open http://localhost:5100 on your phone (use Fully Kiosk Browser for best results).
Status Faces
| State | Emote | Meaning |
|---|---|---|
| Optimal | ( ^_^) |
All systems healthy |
| Warning | ( o_o) |
Something needs attention |
| Critical | ( x_x) |
Immediate action required |
| Notify | ( 'o') |
Transient notification |
| Sleeping | ( -_-)zzZ |
Sleep mode active |
| Disconnected | ( ?.?) |
Can't reach server |
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) |
| docker | Container health and restart loops |
Configuration
Edit config.json to enable/disable detectors and set thresholds:
{
"aggregator_url": "http://localhost:5100",
"detectors": [
{
"name": "disk_space",
"enabled": true,
"script": "detectors/disk_space.py",
"env": {
"CHECK_INTERVAL": "300",
"THRESHOLD_WARNING": "85",
"THRESHOLD_CRITICAL": "95"
}
}
]
}
Custom Detectors
Create your own detector by POSTing events to the aggregator:
curl -X POST http://localhost:5100/event \
-H "Content-Type: application/json" \
-d '{"id": "my_check", "priority": 2, "message": "Something is wrong", "ttl": 120}'
id— Unique identifier for this eventpriority— 1 (critical), 2 (warning), 3 (notify), 4 (optimal)message— What to displayttl— Auto-expire after N seconds (for heartbeat pattern)
Clear an event:
curl -X POST http://localhost:5100/clear \
-d '{"id": "my_check"}'
Home Assistant Integration
Add REST commands to your configuration.yaml:
rest_command:
kao_notify:
url: "http://YOUR_SERVER:5100/notify"
method: POST
content_type: "application/json"
payload: >
{
"message": "{{ message | default('') }}",
"duration": {{ duration | default(5) }},
"emote": "{{ emote | default('') }}",
"color": "{{ color | default('') }}",
"animation": "{{ animation | default('') }}",
"sound": "{{ sound | default('') }}"
}
kao_sleep:
url: "http://YOUR_SERVER:5100/sleep"
method: POST
kao_wake:
url: "http://YOUR_SERVER:5100/wake"
method: POST
Use in automations:
automation:
- 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
emote: "( °o°)"
color: "#FF9900"
sound: "chime"
- alias: "Timer Complete"
trigger:
platform: event
event_type: timer.finished
action:
service: rest_command.kao_notify
data:
message: "Timer done!"
emote: "\\(^o^)/"
animation: "celebrating"
sound: "success"
- alias: "Kao Sleep at Bedtime"
trigger:
platform: time
at: "23:00:00"
action:
service: rest_command.kao_sleep
- alias: "Kao Wake in Morning"
trigger:
platform: time
at: "07:00:00"
action:
service: rest_command.kao_wake
Notify options:
emote: Any ASCII emote (e.g.,( °o°),\\(^o^)/)color: Hex color (e.g.,#FF9900)animation:breathing,shaking,popping,celebrating,floating,bouncing,swayingsound:chime,alert,warning,critical,success,notify,none
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 |
/notify |
POST | Simple notification {"message": "", "duration": 5} |
/sleep |
POST | Enter sleep mode |
/wake |
POST | Exit sleep mode |
/docs |
GET | Interactive API documentation (Swagger UI) |
Full API documentation available at /docs or in openapi.yaml.
Personality
The emote has personality! In optimal state it:
- Rotates through happy faces every 5 minutes
- 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
MIT