{"id":13558688,"url":"https://github.com/soulshake/fonos","last_synced_at":"2025-04-15T16:35:07.943Z","repository":{"id":139777384,"uuid":"77649650","full_name":"soulshake/fonos","owner":"soulshake","description":"Sonos-like multi-room audio setup for Raspberry Pi","archived":false,"fork":false,"pushed_at":"2017-06-20T20:21:11.000Z","size":65,"stargazers_count":31,"open_issues_count":0,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-06T23:43:56.566Z","etag":null,"topics":["ansible","mopidy","pulseaudio","raspberry-pi"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soulshake.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-12-30T01:06:33.000Z","updated_at":"2023-09-15T11:10:49.000Z","dependencies_parsed_at":"2024-01-14T14:32:37.107Z","dependency_job_id":"142fc327-3582-4cea-a61c-a4b7723a1aa4","html_url":"https://github.com/soulshake/fonos","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soulshake%2Ffonos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soulshake%2Ffonos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soulshake%2Ffonos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soulshake%2Ffonos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soulshake","download_url":"https://codeload.github.com/soulshake/fonos/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249109169,"owners_count":21214114,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansible","mopidy","pulseaudio","raspberry-pi"],"created_at":"2024-08-01T12:05:06.093Z","updated_at":"2025-04-15T16:35:07.925Z","avatar_url":"https://github.com/soulshake.png","language":"Shell","funding_links":[],"categories":["Shell","ansible"],"sub_categories":[],"readme":"# Fonos\n\nFonos is an open source multi-room speaker system for Raspberry Pi.\n\n## Dependencies\n\n- a flash card of at least 4GB or so\n- a Raspberry Pi (we're using a Raspberry Pi 3)\n- a way to burn images to SD cards (we recommend [Etcher](https://etcher.io/))\n\n## Setup (local)\n\nNote: the rest of this tutorial assumes you will set the hostname of your Pi to be `fonos`. (If you're setting up more than one Pi, be sure to use a different hostname for each one.)\n\nDownload the [Raspbian Jessie Lite](https://www.raspberrypi.org/downloads/raspbian/) image and burn it to your SD card with Etcher, `dd` or your own favorite method.\n\nWith Etcher CLI, for example:\n\n\u003cpre\u003esudo etcher \u003cb\u003e~/Downloads/2017-04-10-raspbian-jessie-lite.img\u003c/b\u003e --drive \u003cb\u003e/dev/mmcblk0\u003c/b\u003e\u003c/pre\u003e\n\n### Get the SD card devices\n\n#### On Linux\n\nWith the SD card inserted into your computer, list the SD card devices by running `sudo fdisk -l`. On Linux, the output will look something like this:\n\n\u003cpre\u003e\n$ sudo fdisk -l\n\nDisk \u003cb\u003e/dev/mmcblk0\u003c/b\u003e: 14.9 GiB, 15931539456 bytes, 31116288 sectors\n[...]\n\nDevice         Boot Start      End  Sectors  Size Id Type\n\u003cb\u003e/dev/mmcblk0p1\u003c/b\u003e       8192    92159    83968   41M  c W95 FAT32 (LBA)\n\u003cb\u003e/dev/mmcblk0p2\u003c/b\u003e      92160 31116287 31024128 14.8G 83 Linux\n\u003c/pre\u003e\n\nIn the output above:\n- **`/dev/mmcblk0`** is the disk\n- **`/dev/mmcblk0p1`** is the boot device\n- **`/dev/mmcblk0p2`** is the non-boot device\n\n#### On other platforms\n\nFIXME\n\n### Enable SSH\n\n##### Create a target directory on your host if it doesn't exist already:\n\nWe'll use `/media/pi`:\n\n`sudo mkdir -p /media/pi`\n\n##### Mount the SD card's boot device:\n\n\u003cpre\u003esudo mount \u003cb\u003e/dev/mmcblk0p1\u003c/b\u003e /media/pi\u003c/pre\u003e\n\n##### Create an empty file called `ssh` at the root of the boot partition to enable SSH on the Pi:\n\n`sudo touch /media/pi/ssh`\n\n##### Unmount (but don't physically eject the SD card yet):\n\n`sudo umount /media/pi`\n\n\n### Configure the Pi\n\nThe default Pi hostname is `raspberrypi`. We'll give ours a custom hostname, `fonos`, so its web interface can later be accessed at `http://fonos.local`. To do so:\n\n#####  Mount the second (non-boot) SD card device:\n\n\u003cpre\u003esudo mount \u003cb\u003e/dev/mmcblk0p2\u003c/b\u003e /media/pi/\u003c/pre\u003e\n\n##### Change the hostname:\n\n\u003cpre\u003eecho \"\u003cb\u003efonos\u003c/b\u003e\" | sudo tee /media/pi/etc/hostname\u003c/pre\u003e\n\n##### Modify the hosts file to replace `raspberrypi` with your chosen hostname:\n\n\u003cpre\u003esudo sed -i s/raspberrypi/\u003cb\u003efonos\u003c/b\u003e/ /media/pi/etc/hosts\u003c/pre\u003e\n\n##### Network setup:\n\n_If you intend to connect your Pi directly to your router via ethernet, you can skip this step._\n\nIf you want to interact with your Pi over wifi, append the following snippet (with your own SSID and passphrase) to `/media/pi/etc/network/interfaces`:\n\n\n\u003cpre\u003e\niface wlan0 inet dhcp\n        wpa-ssid \"\u003cb\u003eyourCleverWiFiSSID\u003c/b\u003e\"\n        wpa-psk \"\u003cb\u003eyourWiFipassword\u003c/b\u003e\"\n\u003c/pre\u003e\n\n##### Unmount and eject:\n\n`sudo umount /media/pi`\n\nPhysically eject the SD card from your computer and insert it into your Pi.\n\n## Connecting to the Pi\n\nPlug your Pi into a micro USB power source and give it a few minutes to boot.\n\nSSH in as the `pi` user by running \u003cpre\u003essh pi@\u003cb\u003efonos\u003c/b\u003e.local\u003c/pre\u003e. The default password is `raspberry`.\n\nNote: If you still can't connect via SSH after a few minutes, try connecting to your router's web interface to see if the device appears there. If it doesn't, it usually helps to unplug the Pi and plug it back in.\n\n### SSH key authentication\n\n_If you don't mind typing a password every time you SSH to the Pi, you can skip this step._\n\nFrom the Pi, create the `~/.ssh/authorized_keys` file and add your public SSH key. If you don't know how to do this, see the first few steps at the [GitHub tutorial on SSH keys](https://help.github.com/articles/connecting-to-github-with-ssh/).\n\nExit, then SSH to the Pi again to make sure you're not prompted for a password.\n\nOnce you've verified you can `ssh pi@fonos.local` without being prompted for a password, you should disable password login on the Pi:\n\n`echo \"PasswordAuthentication No\" | sudo tee -a /etc/ssh/ssh_config`\n\n\n## Deployment\n\nFrom your **local machine**, complete the following steps:\n\n#### Install Ansible (version 2.0 or above)\n\nSee instructions for installing Ansible [here](http://docs.ansible.com/ansible/intro_installation.html).\n\n#### Clone this repo\n\n`git clone git@github.com:soulshake/fonos.git \u0026\u0026 cd fonos`\n\n#### Create your Ansible inventory file (`hosts`)\n\nFrom the root of the repo you just cloned, copy `hosts.sample` to `hosts` and modify it:\n\n- add your own `spotify_username` and `spotify_password`, and credentials for other services you wish to enable\n- replace the hosts under `[fonos]` with the hostname you chose earlier plus a `.local` extension (in our case, `fonos.local`)\n\nThe resulting `hosts` file should look something like this (if you have Pis with the hostnames `fonos` and `fonos2`):\n\n\u003cpre\u003e\n[fonos]\n\u003cb\u003efonos.local\u003c/b\u003e\n\u003cb\u003efonos2.local\u003c/b\u003e\n\n[fonos:vars]\nspotify_username=\u003cb\u003eyour.spotify.username\u003c/b\u003e\nspotify_password=\u003cb\u003eyourSpotifyPa$$word\u003c/b\u003e\n\u003c/pre\u003e\n\nIf you want to provision more Pis later, just add their hostnames under `[fonos]`.\n\n#### Run the Ansible playbook\n\n`ansible-playbook playbook.yml -i hosts`\n\nOnce the playbook has completed, mopidy should be accessible at [http://fonos.local:6680/mopidy/](http://fonos.local:6680/mopidy/).\n\nNote: For some reason, the playbook sometimes fails the first time at the \"enable systemd units\" step. If this happens, retry by running:\n\n`ansible-playbook playbook.yml -i hosts --start-at-task=\"enable systemd units\"`\n\n## Configuration\n\nConfig files are located on the Pi in `/home/pi/.config/`.\n\n### To view your current config as seen by the Mopidy service\n\nFrom the Pi, run:\n\n- `source /home/pi/fonos/env/bin/activate`\n- `mopidy config`\n\n\n## Troubleshooting\n\nMopidy is running as a systemd user unit. By running as a user service (as opposed to a system service), we can avoid dealing with system config files as much as possible and be self-contained within the `pi` user's home directory.\n\nYou can check the `mopidy` service status, reload it or restart it by running:\n\n- `systemctl --user status mopidy`\n- `systemctl --user reload mopidy`\n- `systemctl --user restart mopidy`\n- etc.\n\nOccasionally the PulseAudio daemon can crash; you can check it by running `systemctl --user status pulseaudio`.\n\n\n### \"I can view the web interfaces but nothing is playing\"\n\nEnsure your credentials are correct in the output of `mopidy config` as described in [Configuration](#Configuration).\n\nTry downloading an mp3 directly to the Pi:\n\n`wget https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3 /home/pi/fonos`\n\nYou should be able to see it under `Files` in the [Moped interface](http://fonos.local:6680/moped), for example. If it plays through your speakers, there might be an issue with your credentials for the service you're trying to play through (e.g. Spotify/Soundcloud/etc).\n\n\n### \"The interface shows that it's playing, but I don't hear any sound\"\n\n- Ensure your Pi is connected to your speaker via audio cable.\n- Ensure your speaker is plugged in and on.\n\nThis may sound obvious, but it happens to the best of us :)\n\n\n### Debug logging\n\nFIXME\n\n\n## Combining sinks\n\nDownload something to play:\n\n`wget https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3 /home/pi/fonos`\n\nThen navigate to `http://fonos.local:6680/moped` in a browser and play the track from the \"Files\" section.\n\nThen, on the Pi, list sinks by name:\n\n```\npacmd list-sinks | grep -i name:\n\tname: \u003calsa_output.0.analog-stereo\u003e\n\tname: \u003ctunnel.vorpal.local.alsa_output.pci-0000_00_03.0.hdmi-stereo\u003e\n\tname: \u003ctunnel.vorpal.local.alsa_output.pci-0000_00_1b.0.analog-stereo\u003e\n\tname: \u003ctunnel.vorpal.local.combined\u003e\n\tname: \u003ctunnel.fonos2.local.alsa_output.0.analog-stereo\u003e\n\tname: \u003ctunnel.fonos2.local.alsa_output.0.analog-stereo.2\u003e\n```\n\nCreate a combined output between our Pi's output (`alsa_output.0.analog-stereo`) and the corresponding output on the other pi (`tunnel.fonos2.local.alsa_output.0.analog-stereo`). (There's duplicates (with a `.2` suffix) because of ipv6.)\n\nCreate a new combined sink:\n\n```\npacmd load-module module-combine-sink \\\n  sink_name=combined \\\n  slaves=\"alsa_output.0.analog-stereo,tunnel.fonos2.local.alsa_output.0.analog-stereo\"\n```\n\nNow if you run `pacmd list-sinks | grep -i name:` again, you'll see the new sink:\n\n```\n\tname: \u003ccombined\u003e\n```\n\nA snippet to combine all USB sinks:\n\n```\npacmd load-module module-combine-sink \\\n  sink_name=combined \\\n  slaves=$(pacmd list-sinks \n    | sed -n 's/^\\s*name: \u003c\\(.*\\)\u003e$/\\1/p' \\\n    | grep -e alsa_output.usb \\\n    | tr \"\\n\" \",\")\n```\n\nOpen pavucontrol from your host with the `PULSE_SERVER` environment variable set to your Pi hostname:\n\n`PULSE_SERVER=fonos.local pavucontrol`\n\nIn the **Playback** tab, you should be able to select the combined output.\n\nAll your speakers should now, in theory, be producing sound.\n\n\n### Troubleshooting\n\nIf you can view the web interface but nothing seems to actually play, you may need to check on the Mopidy or PulseAudio services.\n\nTo view service logs:\n\n- `sudo journalctl _SYSTEMD_USER_UNIT=mopidy.service`\n- `sudo journalctl  _SYSTEMD_USER_UNIT=pulseaudio.service`\n\nTo restart Mopidy:\n\n`systemctl --user restart mopidy.service`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoulshake%2Ffonos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoulshake%2Ffonos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoulshake%2Ffonos/lists"}