A Raspberry Pi alarm clock that wakes you with Spotify or internet radio, controlled via a web UI and scheduled through crontab.
/
curlThe server curls itself — cron runs curl http://hostname:3141/radioalarm or /spotialarm. If Spotify fails, it falls back to radio automatically.
The heart of the project. A Flask app on port 3141 that exposes all alarm, playback, and configuration endpoints. Runs on 0.0.0.0 so it's reachable across the local network.
Key globals: alarm_time, alarm_active, currently_playing, fade_minutes
Alarms are stored as crontab entries tagged with "SPOTIFY ALARM". On save, old entries are removed and a new one is written. On startup, the server reads crontab to restore the active alarm.
The cron job is simply a curl to the server's own /radioalarm or /spotialarm endpoint.
Uses the Spotify Web API via spotipy for playlist metadata and direct HTTP for playback control. Supports Authorization Code Flow with auto token refresh.
access_token.txt + config.iniStreams internet radio via MPD, controlled with mpc shell commands. Pre-configured with NTS Live streams.
mpc play/stop/next/prev/togglemake radio-stationsSystem volume via amixer with L/R channel balance using sqrt power curves. Fade-in/out runs in separate threads, incrementing 1% at a time over configurable minutes.
Default fade: 15 minutes to volume 30%.
On alarm trigger, fires wakeup_alarm_triggered webhook to a local HA instance on port 8123. Enables smart home automations (lights, blinds, etc.) tied to the alarm.
sequenceDiagram
participant U as User Browser
participant S as Flask :3141
participant SP as Spotify API
U->>S: GET /login
S-->>U: 302 Redirect to Spotify
U->>SP: Authorize (scopes: playback)
SP-->>U: Redirect to /authorize_spotify?code=...
U->>S: GET /authorize_spotify?code=abc
S->>SP: POST /api/token (code → tokens)
SP-->>S: access_token + refresh_token
S-->>U: "Tokens saved"
Note over S,SP: Later — token expired
S->>SP: POST /api/token (refresh_token)
SP-->>S: New access_token
| Route | Method | Purpose |
|---|---|---|
/ | GET | Web UI — set alarm time, toggle on/off |
/set_alarm | POST | Set alarm time (hour, minute) from form |
/set_alarm_active | POST | Toggle alarm on/off (JSON body) |
/cronsave | POST | Save alarm to crontab with mode (radio/spotify) |
/cronclean | GET | Remove all alarm cron entries |
/radioalarm | GET | Start radio + fade in + HA webhook |
/spotialarm | GET | Start Spotify + fade in + HA webhook |
/radioplay | GET | Play radio immediately |
/radiostop | GET | Stop radio |
/radionext / /radioprev | GET | Switch radio station |
/spotiplay | GET | Play random track from Spotify playlist |
/spotipause | GET | Pause Spotify |
/stop | GET | Stop all playback (optional fade out) |
/volume?volume=N&balance=B | GET | Set volume (0–100) and optional balance (-1 to 1) |
/volumeup / /volumedown | GET | Adjust volume ±10% |
/login | GET | Start Spotify OAuth flow |
/devices | GET | List Spotify Connect devices |
/areyourunning | GET | Health check (also speaks via espeak) |
music-alarm-clock/ ├── server.py — Flask app, all routes & logic ├── config_reader.py — reads config.ini ├── config.ini — local config (gitignored) ├── config.sample.ini — template for config ├── keyboard_input.py — USB numpad controls ├── keyboard_control.py — keyboard event handler ├── playlists.py — playlist definitions (unused) ├── utilities/ — helper scripts ├── templates/ │ └── index.html — web UI template ├── scripts/ — misc shell scripts ├── test_*.py — pytest test files ├── Makefile — install, deps, radio, dev ├── Dockerfile — local dev container ├── spotify-alarm-clock.service — systemd unit file ├── keyboard-controls.service — systemd for numpad ├── requirements.txt — Python dependencies └── bash_aliases — convenience shell aliases
cp config.sample.ini config.inimake dependenciesmake docker-build && make docker-run for local devlocalhost:3141server.pyespeak/home/pi/Developer/ in service filevirtualenv not uv (legacy)/volumeexcept: blocks hide errors