radio and mpd random audiobook script

This commit is contained in:
kekskurse 2025-07-03 20:43:01 +02:00
parent 1bb2b87ccf
commit 721c9c9596
4 changed files with 264 additions and 0 deletions

View file

@ -0,0 +1,116 @@
title: Random Audiobook on MPD
order: 10
---
I want the Zigbee light switch attached directly to the headboard of my bed to play a random Sherlock Holmes or Three Investigators episode when pressed and held. To do this, I first need to write a Python script that connects to my [MPD](https://de.wikipedia.org/wiki/Music_Player_Daemon) and retrieves a list of all possible audiobooks, then selects one at random and plays it. The script then needs to be made available on the Pi running Home Assistant and linked to the button.
## Setup Homeassistant
I run Homeassistant inside a Docker container. Therefor i need to mount the folder with the python script inside the container. I use the /opt directly in this setup
```
services:
homeassistant:
image: "ghcr.io/home-assistant/home-assistant:stable"
volumes:
- /opt/:/opt/root
...
```
after that i could make the script visible to homeassistant by adding the following content to my `configuration.yaml`:
```
shell_command:
hoerbuch: python3 /opt/root/randomhoerbuch
```
Than i used the Homeassistant GUI to create an automation which run the script on the hold action of the button:
```
alias: Button Bed Long
description: ""
trigger:
- platform: device
domain: mqtt
device_id: cdb815a53346bc4bca446b097a2da234
type: action
subtype: hold
discovery_id: 0x00158d00027d83ba action_hold
condition: []
action:
- service: shell_command.hoerbuch
data: {}
mode: single
```
## Python code
```pthon
#!/bin/python3
import random
from mpd import MPDClient
# Konfigurierbare Einstellungen
MPD_HOST = "192.168.1.101"
MPD_PORT = 6600
MPD_PASSWORD = None # oder z.B. "meinPasswort"
ALBUM_ARTISTS = ["Sherlock Holms - Aus den Tagebüchern"] # Beispielkünstler
def connect_to_mpd():
client = MPDClient()
client.timeout = 10
client.idletimeout = None
client.connect(MPD_HOST, MPD_PORT)
if MPD_PASSWORD:
client.password(MPD_PASSWORD)
return client
def get_albums_by_artist(client, artist):
albums = client.list("album", "albumartist", artist)
return [album for album in albums if album]
def get_songs_from_album(client, artist, album):
# Versuche zuerst mit albumartist
songs = client.find("albumartist", artist, "album", album)
if not songs:
# Fallback auf artist, falls albumartist nicht funktioniert
songs = client.find("artist", artist, "album", album)
print(f"Keine Songs gefunden für:\nArtist: {artist}\nAlbum: {album}")
return songs
def main():
client = connect_to_mpd()
# Alle Alben sammeln
artist_album_map = {}
for artist in ALBUM_ARTISTS:
albums = get_albums_by_artist(client, artist)
if albums:
artist_album_map[artist] = albums
# Zufällig Künstler und Album wählen
if not artist_album_map:
print("Keine Alben gefunden.")
return
artist = random.choice(list(artist_album_map.keys()))
album_entry = random.choice(artist_album_map[artist])
album = album_entry["album"] if isinstance(album_entry, dict) else album_entry
print(f"Spiele zufälliges Album:\nKünstler: {artist}\nAlbum: {album}")
# Songs holen und abspielen
songs = get_songs_from_album(client, artist, album)
client.clear()
for song in songs:
client.add(song["file"])
client.play()
client.close()
client.disconnect()
if __name__ == "__main__":
main()
```

61
projects/radio.md Normal file
View file

@ -0,0 +1,61 @@
title: Radio
---
![Radio Blaupunkt from the Front](./radio_front.jpg)
I got an [Madeira 7.621.250](https://www.radiomuseum.org/r/blaupunkt_madeira_7621250.html) (to get some schematics you may wont to look at [Madeira 7.622.250](https://www.radiomuseum.org/r/blaupunkt_madeira_7622250762225.html)). I got an [Pi 3 B+](https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/) with an [HiFiBerry AMP 2](https://www.hifiberry.com/shop/boards/amp2/). I soldered two cables to the existing solder joints on the speaker. This makes it possible to reverse the modifications I made with the PI, as it did not damage the “original” radio. I then screwed these cables, as well as the cables from a 12 V power supply (make sure it has sufficient amperage), into the AMP2's luster terminals. I also connected an [LM2596S](https://www.az-delivery.de/en/products/lm2596s-dc-dc-step-down-modul-1) module to the 12 V power supply, which I later used for the neon pixels. Presumably, the few WS2812 LEDs should even be able to be supplied with sufficient power by the PI itself.
The radio has two light bulbs that are used for backlighting the scaler. I attached two [NeoPixel sticks with eight WS2812 5050 RGB LEDs (DEBO LED NP8 2)](https://www.reichelt.de/de/de/shop/produkt/entwicklerboards_-_neopixel_stick_mit_8_ws2812_5050_rgb_leds-240688) to the light bulb mounts, also in such a way that the original light bulbs are not damaged. I connected these to GPIO 12, making sure to note which GPIOs are [used by the AMP2](https://www.hifiberry.com/docs/hardware/gpio-usage-of-hifiberry-boards/) and where a digital converter is still available. This completes the hardware modifications for the first version.
## AMP2 and MPD Setup
There are a log of different tutorials how to connect the AMP2 to the PI. For me the following steps works:
Edit the `/boot/firmware/config.txt` file by disabeld the audio=on and add the amp overlay:
```
# Enable audio (loads snd_bcm2835)
//dtparam=audio=on
dtoverlay=hifiberry-amp2
```
with this change (and after a restart) i could run `aplay -l` and got the following output:
```
**** List of PLAYBACK Hardware Devices ****
card 0: vc4hdmi [vc4-hdmi], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: sndrpihifiberry [snd_rpi_hifiberry_dacplus], device 0: HiFiBerry DAC+ HiFi pcm512x-hifi-0 [HiFiBerry DAC+ HiFi pcm512x-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
```
The next step was to install the MPD by calling `sudo apt-get install mpd`. To allow the MPD to connect to the pipewrie it need to run in the user account, so I enabled and start it with the following commands:
```
systemctl disable mpd
systemctl stop mpd
systemctl --user enable mpd
systemctl --user start mpd
```
inside the mpd config `/etc/mpd.conf` i enabled the audio output for pulse/pipewire
```
audio_output {
type "pulse"
name "My Pulse Output"
## server "remote_server" # optional
## sink "remote_server_sink" # optional
## media_role "media_role" #optional
}
```
and change the path to my mp3 files. As MPD Client i use [rmpc](https://mierak.github.io/rmpc/) on linux as cli client and [M.A.L.P](https://gitlab.com/gateship-one/malp) from fdroid for android. And also a [Light switch](/projects/homeassistant/random-audiobook-mpd.html).
## Setup Neonpixel
I want that the Radio turn the radio just on if the MPD is playing something. So i use [this python script](/projects/radio_led.py) that will show and rainbowl animation if the mpd is currenlty playing and turn the leds of if the music is paused for more than one minute.

BIN
projects/radio_front.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,018 KiB

87
projects/radio_led.py Normal file
View file

@ -0,0 +1,87 @@
import time
import board
import neopixel
from mpd import MPDClient, CommandError, ConnectionError
import traceback
# === NeoPixel Setup ===
pixel_pin = board.D12
num_pixels = 16
ORDER = neopixel.GRB
pixels = neopixel.NeoPixel(
pixel_pin, num_pixels, brightness=0.2, auto_write=False, pixel_order=ORDER
)
# === Regenbogen Effekt ===
def wheel(pos):
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (int(pos * 3), int(255 - pos * 3), 0)
if pos < 170:
pos -= 85
return (int(255 - pos * 3), 0, int(pos * 3))
pos -= 170
return (0, int(pos * 3), int(255 - pos * 3))
def rainbow_cycle(wait):
for j in range(255):
for i in range(num_pixels):
pixel_index = (i * 256 // num_pixels) + j
pixels[i] = wheel(pixel_index & 255)
pixels.show()
time.sleep(wait)
# === MPD Verbindung ===
def get_mpd_state():
client = MPDClient()
try:
client.connect("localhost", 6600)
status = client.status()
return status.get("state", "stop")
except (ConnectionError, CommandError, Exception) as e:
print(f"[MPD Fehler] {e}")
traceback.print_exc()
return "error"
finally:
try:
client.close()
client.disconnect()
except:
pass
# === Haupt-Daemon ===
def main():
last_play_time = 0
lights_on = False
while True:
state = get_mpd_state()
now = time.time()
if state == "play":
last_play_time = now
lights_on = True
rainbow_cycle(0.001) # schneller Regenbogenzyklus
else:
if lights_on and (now - last_play_time > 60):
pixels.fill((0, 0, 0))
pixels.show()
lights_on = False
else:
time.sleep(1) # MPD ist pausiert oder gestoppt, aber innerhalb 60 Sek.
time.sleep(0.5) # MPD-Check-Intervall
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pixels.fill((0, 0, 0))
pixels.show()
print("Beendet.")