07:30

music-alarm-clock

a raspberry pi alarm clock you actually want to wake up to

Flask Spotify API MPD / Radio Crontab Raspberry Pi Home Assistant
scroll

A Flask server that turns a Raspberry Pi into a music alarm clock

Set a wake-up time via a dark-mode web UI served on port 3141. At the scheduled moment, the Pi fades volume in over 15 minutes and starts playing — either a Spotify playlist via the Spotify Connect API, or internet radio via MPD/MPC. If Spotify's device goes missing, it falls back to radio automatically. A crontab entry fires a curl to the local server, so the alarm persists across reboots. Optional numpad controls let you physically play/pause, skip stations, and set volume without ever touching a screen.

How the pieces connect

core Flask Server

HTTP API with ~20 routes. Manages alarm state, Spotify OAuth, volume control, radio playback, and cron scheduling. Global state for alarm_time, alarm_active, and currently_playing.

server.py — 650 lines
interface Web UI

Dark Jinja2 template with time picker, alarm toggle, volume/balance sliders, and playback buttons.

templates/index.html
scheduling Crontab

python-crontab writes a job tagged "SPOTIFY ALARM" that curls /radioalarm or /spotialarm at wake time.

python-crontab lib
input Numpad Controls

pynput listener maps USB numpad keys to play, pause, volume, and station switching.

keyboard_input.py
music · spotify Spotify Connect

OAuth Authorization Code flow. Picks a random track from a playlist via spotipy, pushes playback to a hardcoded device ID.

server.py → spotify_request()
music · radio MPD / MPC

Subprocess calls to mpc for play/stop/next/prev. Stations added via mpc add. NTS Live configured by default.

Makefile → radio-stations
audio Volume & Fade

amixer controls with stereo balance. Threaded fade-in/fade-out over configurable duration (default 15 min).

server.py → fade_volume_in()
integration Home Assistant

Fires a "wakeup_alarm_triggered" webhook on alarm start — for smart lights, etc.

server.py → homeassistant_triggerWebhook()
config INI Config

Spotify credentials, device ID, playable URI, and error preferences in config.ini.

config_reader.py + config.ini

What lives where

server.py # the whole backend — routes, Spotify, radio, volume, cron keyboard_input.py # pynput numpad listener for physical controls config_reader.py # reads config.ini values config.sample.ini # template for Spotify credentials & settings templates/ index.html # the alarm clock web UI (Jinja2) utilities/ # helper scripts scripts/ # misc automation Makefile # install, dependencies, radio stations, dev commands Dockerfile # local dev on non-Pi machines requirements.txt # Python deps (flask, spotipy, python-crontab, pynput…)

Built with

Flask web server
spotipy spotify api
MPD/MPC radio playback
python-crontab scheduling
pynput keyboard input
amixer volume control
systemd service mgmt
Docker local dev

HTTP routes

All routes live in server.py. The web UI talks to these over XHR. The crontab alarm fires via curl.

Route
Purpose
GET /
Web UI — alarm time picker, volume, playback controls
POST /set_alarm
Set alarm hour + minute, write to crontab
POST /cronsave
JSON alarm save with mode (radio/spotify) + fade config
GET /radioalarm
Trigger radio alarm — fade in + mpc play + HA webhook
GET /spotialarm
Trigger Spotify alarm — fade in + random track + fallback to radio
GET /radioplay
Start radio playback
GET /radionext
Switch to next radio station
GET /stop
Stop all playback (radio + Spotify)
GET /volume
Set volume (0–100) with optional stereo balance (-1 to 1)
GET /login
Start Spotify OAuth flow
GET /devices
List Spotify Connect devices

Good places to start

The codebase is intentionally small — one main file, one template, one config. That means changes land fast and you can hold the whole thing in your head.

Modernize to pyproject.toml + uv

Replace virtualenv + requirements.txt with uv and a pyproject.toml. Update Makefile targets accordingly.

easy

Unhardcode the deploy path

The systemd service and Makefile assume ~/Developer/spotify-alarm-clock. Make this configurable or auto-detect.

easy

Multiple alarm support

Currently one alarm at a time. The crontab layer can hold many — the UI and state management need to catch up.

medium

Proper state persistence

Alarm state is global Python variables. A reboot or crash loses the in-memory state. Consider SQLite or a simple JSON file.

medium

Fix volume/play race condition

The fade-in thread and play command can race — volume might "flash" loud before the fade starts. The 3s sleep is a workaround, not a fix.

medium

Refactor server.py

650 lines doing routing, Spotify OAuth, volume math, cron management, and radio control. Good candidate for splitting into modules.

hard

Running locally

terminal
# clone git clone https://github.com/janek/spotify-alarm-clock cd spotify-alarm-clock # configure cp config.sample.ini config.ini # edit config.ini with your Spotify credentials # option A: docker (macOS/Linux — no Pi needed) make docker-build make docker-run # → http://localhost:3141 # option B: on a Raspberry Pi make dependencies sudo make install make radio-stations make dev