Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
cef6141
base updates to qrplay & qrgen
Mar 13, 2018
35bc39e
smaller updates to main py scripts
Mar 13, 2018
31b773c
smaller updates to main py scripts
dernorberto Mar 13, 2018
5e2e7bd
Merge branch 'master' of github.com:dernorberto/qrocodile
dernorberto Mar 13, 2018
dca6a2b
added getjson files
Mar 17, 2018
178b925
play album = OK
Mar 17, 2018
62dfe2f
qrgen generates albums
dernorberto Mar 18, 2018
7178f6a
added spotipy to qrplay
dernorberto Mar 18, 2018
938b6e3
getting zones
dernorberto Mar 18, 2018
ef26c30
extracting list of available sonos zones
dernorberto Mar 22, 2018
e804245
deleted dev files
dernorberto Mar 22, 2018
6b19885
updated gitignore
dernorberto Mar 22, 2018
30bee84
added dev_ files
dernorberto Mar 22, 2018
9c837f8
trimmed getjson
dernorberto Mar 22, 2018
3052307
added sonos zones
dernorberto Mar 22, 2018
6163eb6
generating basic zone cards
dernorberto Mar 22, 2018
6255709
enabled log
dernorberto Mar 23, 2018
d162f07
creating zone cards
dernorberto Mar 24, 2018
dd224dc
updates
dernorberto Mar 24, 2018
5a2a762
sonos zones with larger label
dernorberto Mar 24, 2018
d7d49da
first changes adding playlist support
dernorberto Mar 24, 2018
e6a00bc
added dev files
dernorberto Mar 25, 2018
fcaa169
qrgen generates playlist cards
dernorberto Mar 26, 2018
db0cf64
fixed elif for playlist
dernorberto Mar 26, 2018
b316885
qrplay minor updates
dernorberto Mar 26, 2018
2feff93
working playlist playback
dernorberto Mar 26, 2018
738959a
fixes to qrplay changezone
dernorberto Mar 26, 2018
d8476be
updated README, fixed qrgen issues with songs
dernorberto Mar 27, 2018
4a5dc9e
updated README, fixed qrgen issues with songs
dernorberto Mar 27, 2018
b3304e6
Merge branch 'master' of github.com:dernorberto/qrocodile
dernorberto Mar 27, 2018
49df560
added volume control when changing rooms
dernorberto Mar 30, 2018
3afbc8b
removed dev files, updated .gitignore
dernorberto Mar 30, 2018
61b083d
Revert "added volume control when changing rooms"
dernorberto Mar 30, 2018
a098c57
created command_cards.txt
dernorberto Mar 30, 2018
0363634
generating command cards from separate file and --commands option
dernorberto Mar 30, 2018
df1b963
generating command cards from separate file and --commands option
dernorberto Mar 30, 2018
9c7f294
fixed labels on command cards
dernorberto Mar 30, 2018
70e2685
fixed playlists in qrplay, shortened command_cards.txt
dernorberto Apr 1, 2018
e9c82a3
simplified command card handling
dernorberto Apr 1, 2018
19c7f31
generating default config for qrplay via qrgen
dernorberto Apr 1, 2018
0d2c331
fixed command cards slash for repeat and shuffle
dernorberto Apr 1, 2018
4640b95
allow qrgen.py to read defaults from my_defaults.txt
dernorberto Apr 1, 2018
fcd2681
updated README.md
dernorberto Apr 1, 2018
f0f67c4
removed junk files
dernorberto Apr 1, 2018
1c5619d
getting original cards.css
dernorberto Apr 1, 2018
2136587
fixed issue with /speak
dernorberto Apr 9, 2018
d4f1ad4
fixed qrgen.py failing to start without my_defaults.txt file
dernorberto May 13, 2018
bf602d0
added missing python module pypng
dernorberto May 25, 2018
f230669
added out/ folder to gitignore
dernorberto May 31, 2018
51cc880
added default_host check
dernorberto Jun 4, 2018
4405932
bugfixes qrplay
dernorberto Jun 4, 2018
63505c7
bugfixes qrplay
dernorberto Jun 4, 2018
020f885
bugfixes qrplay
dernorberto Jun 4, 2018
486ada1
bugfixes qrplay
dernorberto Jun 4, 2018
16aae01
bugfixes qrplay
dernorberto Jun 4, 2018
2ef7472
bugfixes qrplay
dernorberto Jun 4, 2018
7248eee
fixed qrplay.py flow
dernorberto Jun 5, 2018
48de007
cleanup of functions, added unidecode to handle special characters, c…
dernorberto Jun 8, 2018
90563bf
added charset=utf-8 to html <head>
dernorberto Jun 9, 2018
40b7af0
qrgen generates artist cards & qrplay plays artists top tracks
dernorberto Jun 9, 2018
651042b
qrgen generates artist cards & qrplay plays artists top tracks
dernorberto Jun 9, 2018
aca6740
Merge branch 'master' of github.com:dernorberto/qrocodile
dernorberto Jun 9, 2018
1f9b373
added logging to qrplay.log
dernorberto Jun 9, 2018
f7b02da
Merge branch 'master' of github.com:dernorberto/qrocodile
dernorberto Jun 9, 2018
6bf5267
updated README.md with more detailed instructions, added some details…
dernorberto Jun 9, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
out
#out
.cache*
.last-device
.DS_Store
*.log
my_defaults.txt
out/
242 changes: 184 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,85 @@

A kid-friendly system for controlling Sonos with QR codes.

## Origin

This is a fork of the *qrocodile* project originally developed by https://github.com/chrispcampbell

Changes to the original:
* support for Spotify **Albums** & **Playlists** & **Artists** (using the Sonos Queue)
* generation of cards for the Sonos Zones (Rooms) available on your network
* reliance on a my_defaults.txt file containing custom defaults for Sonos Room, Spotify User and node-sonos host
* Using a python module instead of an OS package for the generation of QR codes
* replaced urllib with **requests** python module
* updated to python 3.x
* disabled use of webkit2png
* added variables for default announcement volume and language
* separated card creation files between: Commands, Music (albums, artists, playlists) and Sonos Rooms
* added shuffle on/off and repeat on/off commands

For the whole story behind qrocodile, see the original project https://github.com/chrispcampbell/qrocodile

## What Is It?

On the hardware side, it's just a camera-attached Raspberry Pi nested inside some green LEGO and running some custom software that scans QR codes and translates them into commands that control your Sonos system:
On the hardware side, it's just a camera-attached Raspberry Pi nested inside some LEGO and running some custom software that scans QR codes and translates them into commands that control your Sonos system:

<p align="center">
<img src="docs/images/qroc-photo-front.jpg" width="40%" height="40%"> <img src="docs/images/qroc-photo-back.jpg" width="40%" height="40%">
</p>

On the software side, there are two separate Python scripts:

* Run `qrgen.py` on your primary computer. It takes a list of songs (from your local music library and/or Spotify) and commands (e.g. play/pause, next) and spits out an HTML page containing little cards imprinted with an icon and text on one side, and a QR code on the other. Print them out, then cut, fold, and glue until you're left with a neat little stack of cards.
* Run `qrgen.py` on any computer. It takes a list of songs (from your local music library and/or Spotify), commands (e.g. play/pause, next) or sonos zones and spits out an HTML page containing little cards imprinted with an icon and text on one side, and a QR code on the other. Print them out, then cut, fold, and glue until you're left with a neat little stack of cards.

* Run `qrplay.py` on your Raspberry Pi. It launches a process that uses the attached camera to scan for QR codes, then translates those codes into commands (e.g. "speak this phrase", "play [song|artist|album|playlist] in this room", "build a queue").

Requirements:

* Spotify developer account (in order to play content from spotify)
* Python 3
* Python modules:
* spotipy
* requests
* pyqrcode
* pypng


## Spotify Account

* Run `qrplay.py` on your Raspberry Pi. It launches a process that uses the attached camera to scan for QR codes, then translates those codes into commands (e.g. "speak this phrase", "play [song] in this room", "build a queue").
Both for generating cards and for playing content, you will need:
* Spotify Premium account
* [Spotify Developer account](https://developer.spotify.com/my-applications/#!/applications/create) (free) and create an application

## Installation and Setup

### 1. Prepare your Raspberry Pi
### 1. node-sonos-http-api

This node service allows to interact with the available sonos speakers via an API.

To install, clone the [custom fork](https://github.com/chrispcampbell/node-sonos-http-api/tree/qrocodile), check out the `qrocodile` branch, install, and start:

```
% git clone -b qrocodile https://github.com/chrispcampbell/node-sonos-http-api.git
% cd node-sonos-http-api
% npm install --production
% npm start
```

On Debian, the **nodejs-legacy** package was required as well as the standard **nodejs** package.
The node server will start listening on port :5005 of the computer it is running on.

In order
For the announcements, it uses Google TTS, see [node-sonos-http-api](https://github.com/chrispcampbell/node-sonos-http-api/tree/qrocodile) for all the details.

### 2. Prepare your Raspberry Pi to read qrcodes with qrplay.py

I built this using a Raspberry Pi 3 Model B (running Raspbian) and an Arducam OV5647 camera module. Things may or may not work with other models (for example, how you control the onboard LEDs varies by model).
The control of the LEDs will differ based on the Raspberry Pi in use. In my setup, I am using a RPi 3 Model B to run both node and qrplay (although a bit slower, qrplay.py also worked well on a Raspberry Pi Zero W) and an Arducam OV5647 camera module.

To set up the camera module, I had to add an entry in `/etc/modules`:
To set up the camera module, I had to:

* connect the camera to the slot on the RPi
* enable the camera via **raspi-config**
* add an entry in `/etc/modules`:

```
% sudo emacs /etc/modules
Expand All @@ -41,94 +99,172 @@ Next, install `zbar-tools` (used to scan for QR codes) and test it out:

Optional: Make a little case to hold your RPi and camera along with a little slot to hold a card in place.

### 2. Start `node-sonos-http-api`
### 3. Generate some cards with `qrgen`

Currently `qrplay` relies on a [custom fork](https://github.com/chrispcampbell/node-sonos-http-api/tree/qrocodile) of `node-sonos-http-api` that has been modified to do things like:
First, clone the `qrocodile` repo if you haven't already on your primary computer:

* look up library tracks using only a hash string (to keep the QR codes simple)
* return a list of all available library tracks and their associated hashes
* speak the current/next track
* play the album associated with a song
* other things I'm forgetting at the moment
```
% git clone https://github.com/dernorberto/qrocodile
% cd qrocodile
```

(Note: `node-sonos-http-api` made it easy to bootstrap this project, as it already did much of what I needed. However, it would probably make more sense to use something like [SoCo](https://github.com/SoCo/SoCo) (a Sonos controller API for Python) so that we don't need to run a separate server, and `qrplay` could control the Sonos system directly.)
#### 3.1 create music cards

It's possible to run `node-sonos-http-api` directly on the Raspberry Pi, so that you don't need an extra machine running, but I found that it's kind of slow this way (especially when the QR scanner process is already taxing the CPU), so I usually have it running on a separate machine to keep things snappy.
Create a text file that lists the different music cards you want to create. (See `example.txt` for some possibilities.)

To install, clone my fork, check out the `qrocodile` branch, install, and start:
Spotify track/album/playlist URIs can be found in the Spotify app by clicking a song|album|playlist, then selecting "Share > Copy Spotify URI". For `qrgen` to access your Spotify account, you'll need to set up your own Spotify app token. (More on that in the `spotipy` [documentation](http://spotipy.readthedocs.io/en/latest/).)

```
% git clone -b qrocodile https://github.com/chrispcampbell/node-sonos-http-api.git
% cd node-sonos-http-api
% npm install --production
% npm start
% python3 qrgen.py --generate-images --input=<file containing URIs>
% open out/index.html
```

### 3. Generate some cards with `qrgen`

First, clone the `qrocodile` repo if you haven't already on your primary computer:
Without a my_defaults.txt file, you will need to indicate `--hostname` and `--spotify-user` arguments.

```
% git clone https://github.com/chrispcampbell/qrocodile
% cd qrocodile
% python3 qrgen.py --hostname <IP of node-sonos-http-api host> --input example.txt --generate-images --spotify-user <spotify username>
% open out/index.html
```

Also install `qrencode` via Homebrew:
The **out** folder will contain an **index.html** with the generated content. If ran on the RPi, it can be handy to access that content via HTTP, Install nginx (or any other webserver) and edit the default vhost config, like for nginx:

```
% brew install qrencode

root /home/pi/git/qrocodile/out/;

location / {
try_files $uri $uri/ =404;
index index.html zones.html commands.html;
}
```

Spotify track URIs can be found in the Spotify app by clicking a song, then selecting "Share > Copy Spotify URI". For `qrgen` to access your Spotify account, you'll need to set up your own Spotify app token. (More on that in the `spotipy` [documentation](http://spotipy.readthedocs.io/en/latest/).)
#### 3.2 generate my_defaults.txt file

You can use `qrgen` to list out URIs for all available tracks in your music library (these examples assume `node-sonos-http-api` is running on `localhost`):
You can use `qrgen` to generate a file containing your defaults, you will be asked for the Spotfy username, the IP address of the server running the node-sonos-http-api and the default Sonos room. The last used Sonos Room will override the Default.

```
% python qrgen.py --hostname localhost --list-library
% python3 qrgen.py --set-defaults
```

Next, create a text file that lists the different cards you want to create. (See `example.txt` for some possibilities.)

Finally, generate some cards and view the output in your browser:
#### 3.3 generate Sonos Zones/Rooms cards

The cards for Commands and Sonos Zones are generated separately.

Create Sonos Zone cards using `qrgen`, it does not require a list file. Omit `--hostname` if you created a defaults file.

```
% python qrgen.py --hostname localhost --input example.txt
% open out/index.html
% python3 qrgen.py --zones --hostname <IP of node-sonos-http-api host>
% open out/zones.html
```

It'll look something like this:
#### 3.4 generate Commands cards

Create Command cards using qrgen and the text file command_cards.txt. All available commands are in the command_cards_all.txt, place the ones you need in command_cards.txt and generate the cards.

```
% python3 qrgen.py --commands
% open out/commands.html
```

<p align="center">
<img src="docs/images/sheet.jpg" width="50%" height="50%">
</p>

### 4. Start `qrplay`

On your Raspberry Pi, clone this `qrocodile` repo:

```
% git clone https://github.com/chrispcampbell/qrocodile
% git clone https://github.com/dernorberto/qrocodile
% cd qrocodile
```

Then, launch `qrplay`, specifying the hostname of the machine running `node-sonos-http-api`:

```
% python qrplay.py --hostname 10.0.1.6
% python3 qrplay.py --hostname <IP of node-sonos-http-api host>
```

If you want to use your own `qrocodile` as a standalone thing (not attached to a monitor, etc), you'll want to set up your RPi to launch `qrplay` when the device boots:
### 5. start services on boot for a stand-alone RPi

#### 5.1 qrplay using systemd

If you want to use your own `qrocodile` as a standalone thing (not attached to a monitor, etc), you'll want to set up your RPi to launch `qrplay` when the device boots. Here's a **qrplay.service** systemd unit file:

```
% emacs ~/.config/lxsession/LXDE-pi/autostart
# Add an entry to launch `qrplay.py`, pipe the output to a log file, etc
[Unit]
Description=qrplay
After=network.target node-sonos-http-api.service

[Service]
Type=simple
Restart=always
PIDFile=/run/qrplay.pid
#ExecStart=/home/pi/scripts/qrplay.sh
ExecStart=/usr/bin/python3 /home/pi/git/qrocodile/qrplay.py
WorkingDirectory=/home/pi/git/qrocodile/
User=pi
Environment="SPOTIPY_CLIENT_ID=<your spotify client id>" "SPOTIPY_CLIENT_SECRET=<your spotify client secret>"

[Install]
WantedBy=multi-user.target
```

then run
```
% systemctl reload-daemon
% systemctl enable qrplay.service
```

#### 5.2 node-sonos-http-api

It is possible to keep this node server running on boot and restart if it stops working using [forever](https://github.com/foreverjs/forever). Here's part of a /etc/init.d/node-sonos-http-api file that starts the node server using forever.

```
...
APPLICATION_PATH="/home/pi/git/node-sonos-http-api/server.js"
PIDFILE="/var/run/$NAME.pid"
...
forever \
--pidFile $PIDFILE \
-a \
-l $LOGFILE \
--minUptime $MIN_UPTIME \
--spinSleepTime $SPIN_SLEEP_TIME \
start $APPLICATION_PATH 2>&1 > /dev/null &
...
```

#### 5.3 generate zone cards on boot

If you'd like to have fresh sonos zone cards on every boot, you can have a **zones.service** systemd unit file:

```
[Unit]
Description=qrgen zones
After=network.target qrplay.service node-sonos-http-api.service

[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /home/pi/git/qrocodile/qrgen.py --zones
RemainAfterExit=false
WorkingDirectory=/home/pi/git/qrocodile/
User=pi

[Install]
WantedBy=multi-user.target
```

Then run
```
% systemctl reload-daemon
% systemctl enable zones.service
```


## The Cards

Currently `qrgen` and `qrplay` have built-in support for two different kinds of cards: song cards, and command cards.
Currently `qrgen` and `qrplay` have built-in support for six different kinds of cards: song, artist, album, playlist, commands and zone cards

Song cards can be generated for tracks in your music library or from Spotify, and can be used to play just that song, add that song to the queue, or play the entire album. For example:
Song cards can be generated for tracks in your music library or from Spotify, and can be used to play just that song, add that song to the queue, or play the entire album. For example:

<p align="center">
<img src="docs/images/song.png" width="40%" height="40%" style="border: 1px #ddd solid;">
Expand Down Expand Up @@ -156,26 +292,16 @@ Command cards are used to control your Sonos system, performing actions like swi
<img src="docs/images/cmd-whatsong.png" width="20%" height="20%" style="border: 1px #ddd solid;"> <img src="docs/images/cmd-whatnext.png" width="20%" height="20%" style="border: 1px #ddd solid;">
</p>

## The Backstory

It all started one night at the dinner table. The kids wanted to put an album on the turntable (hooked up to the line-in on a Sonos PLAY:5 in the dining room). They're perfectly capable of putting vinyl on the turntable all by themselves, but using the Sonos app to switch over to play from the line-in is a different story.

I was lamenting aloud the number of steps it takes and then I started pondering solutions. Take off my tin foil hat and give in to the Alexa craze? Buy some sort of IoT button thing? An RFID tag thing? QR codes maybe? The latter option got me thinking of all kinds of possibilities. Maybe the kids could choose dinner music from any number of songs/albums (from Spotify or my local collection) just by waving a QR code in front of something. Or maybe now they could build their own dance party playlists.

It seemed like a fun thing to explore, so I ordered a Raspberry Pi and a cheap camera. The next day it arrived and the hacking began.

## Acknowledgments

This was a fun little project to put together mainly because other folks already did much of the hard work.
This project was a great inspiration to develop something that my kids can use. It was a relatively easy project because the groundwork was already laid down by chriscampbell and a series of other people.

Hearty thanks to the authors of the following libraries:
Many thanks to the authors' following libraries and tools:

* [qrencode](https://github.com/fukuchi/libqrencode)
* [qrocodile](https://github.com/chrispcampbell/qrocodile)
* [node-sonos-http-api](https://github.com/jishi/node-sonos-http-api)
* [spotipy](https://github.com/plamere/spotipy)
* [webkit2png](https://github.com/paulhammond/webkit2png)

Thanks also to my kids and wife for all the help with building, printing, cutting, folding, gluing, testing, and filming.

## License

Expand Down
19 changes: 19 additions & 0 deletions command_cards.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{ "cmd:shuffle/on" :
{"command": "cmd:shuffle/on", "label": "Shuffle ON", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_shuffle_black_48dp.png"
},
"cmd:shuffle/off" :
{"command": "cmd:shuffle/off", "label": "Shuffle OFF", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_shuffle_black_48dp.png"
},
"cmd:repeat/on":
{"command": "cmd:repeat/on", "label": "Repeat ON", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_repeat_black_48dp.png"
},
"cmd:repeat/off":
{"command": "cmd:repeat/off", "label": "Repeat OFF", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_repeat_black_48dp.png"
},
"cmd:playpause":
{"command": "cmd:playpause", "label": "Play / Pause", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_pause_circle_outline_black_48dp.png"
},
"cmd:next":
{"command": "cmd:next", "label": "Skip to Next Song", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_skip_next_black_48dp.png"
}
}
28 changes: 28 additions & 0 deletions command_cards_all.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{ "cmd:shuffle/on" :
{"command": "cmd:shuffle/on", "label": "Shuffle", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_shuffle_black_48dp.png"
},
"cmd:shuffle/off" :
{"command": "cmd:shuffle/off", "label": "Shuffle", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_shuffle_black_48dp.png"
},
"cmd:repeat/on":
{"command": "cmd:repeat/on", "label": "Repeat", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_repeat_black_48dp.png"
},
"cmd:repeat/off":
{"command": "cmd:repeat/off", "label": "Repeat", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_repeat_black_48dp.png"
},
"cmd:playpause":
{"command": "cmd:playpause", "label": "Play / Pause", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_pause_circle_outline_black_48dp.png"
},
"cmd:next":
{"command": "cmd:next", "label": "Skip to Next Song", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_skip_next_black_48dp.png"
},
"cmd:saysong":
{"command": "cmd:saysong", "label": "What's Playing?", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/action/drawable-xxxhdpi/ic_help_outline_black_48dp.png"
},
"cmd:saynext":
{"command": "cmd:saynext", "label": "What's Next?", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/action/drawable-xxxhdpi/ic_help_outline_black_48dp.png"
},
"mode:buildqueue":
{"command": "mode:buildqueue", "label": "Build List of Songs", "image": "https://raw.githubusercontent.com/google/material-design-icons/master/av/drawable-xxxhdpi/ic_playlist_add_black_48dp.png"
}
}
Loading