- HTML 63.5%
- Python 35.8%
- Dockerfile 0.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .idea | ||
| docs | ||
| templates | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .python-version | ||
| .woodpecker.yml | ||
| CLAUDE.md | ||
| config.py | ||
| database.py | ||
| docker-compose.example.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| ha_client.py | ||
| LICENSE | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| sample-config.yaml | ||
| scheduler.py | ||
| server.py | ||
| uv.lock | ||
ha-presence-simulation
A FastAPI service that generates randomized light schedules for home presence simulation while on vacation. Schedules are stored in SQLite and fired directly against the Home Assistant REST API via APScheduler — no calendar middleware required.
How It Works
Each light group has one or more time windows per day. For each window, a single on/off event is generated per day:
- Start time:
start_time+ random0..fidget_minutesoffset - Duration:
duration_minutes±20%, randomized each run
Events are stored in a local SQLite database and scheduled with APScheduler, which fires light.turn_on and light.turn_off calls directly against the HA REST API at the right times. On server restart, all future jobs are re-registered from the database automatically.
Setup
Prerequisites
- A running Home Assistant instance with a Long-Lived Access Token
- To create one: Profile → Security → Long-Lived Access Tokens → Create Token
Configuration
Copy .env.example to .env and fill in your values:
cp .env.example .env
HA_URL=http://homeassistant.local:8123
HA_TOKEN=your-long-lived-access-token
TZ=America/Los_Angeles
Create a config/ directory and copy sample-config.yaml into it as config.yaml:
mkdir config
cp sample-config.yaml config/config.yaml
Edit config/config.yaml to define your light groups, brightness levels, and time windows:
light_groups:
- name: Family Room
brightness: 255 # 0-255
entities:
- light.family_room_lamps
windows:
- start_time: '18:30' # earliest on-time
fidget_minutes: 30 # random 0-30 min added to start_time
duration_minutes: 90 # on-duration base; actual is ±20% of this
config/ and data/ are gitignored — they hold your personal configuration and runtime database and should not be committed.
Docker (recommended)
cp docker-compose.example.yml docker-compose.yml
docker compose up -d
docker-compose.example.yml:
services:
presence-simulation:
image: codeberg.org/johnsturgeon/ha-presence-simulation:latest
ports:
- "8000:8000"
volumes:
- ./config:/config
- ./data:/data
env_file: .env
restart: unless-stopped
./config (containing config.yaml) is mounted at /config inside the container. The SQLite database is written to ./data/ on the host, mounted at /data. Both directories survive container restarts and image updates.
Bare-metal
Requires Python 3.14+ and uv.
uv sync
uv run python main.py
The server starts on http://localhost:8000.
To keep it running persistently, use a process manager. Example systemd unit:
[Unit]
Description=HA Presence Simulation
After=network.target
[Service]
WorkingDirectory=/path/to/ha-presence-simulation
ExecStart=/path/to/ha-presence-simulation/.venv/bin/python main.py
Restart=always
[Install]
WantedBy=multi-user.target
Web UI
| Page | Path | Description |
|---|---|---|
| Schedule | / |
View upcoming events grouped by day; generate new schedules; delete individual events |
| Config | /config |
Edit light groups, entities, windows, and brightness; refresh HA entity cache |
| iCal feed | /calendar.ics |
Subscribe in any calendar app (Apple Calendar, Google Calendar, etc.) |
Docs
Notes
- Schedule generation is additive — generating a new range adds to existing events; use Clear All or delete individual rows if you want to replace.
- The entity list on the Config page is cached from HA (refreshed every 30 minutes). Use the ↻ Refresh Entities button to pull immediately.
.envis gitignored — see.env.examplefor required variables.