This is an old revision of the document!
Sonos API for Seeburg
This website is no longer available.
Having solved the electronics problem I developed a small Python program to handle the decoding logic and the communication with the home media system.
The main loop of the program polls the GPIO pin which is connected to the voltage regulator. When there is a pulse, the GPIO pin registers as “true”. The pseudo-code looks like this:
Wait for a pulse When a pulse is detected, count the pulses in the pulse train a) Count the pulses before the “gap” and store this as the selection number b) Count the pulses after the “gap” and store this as the selection letter Play the song corresponding to that selection Go back to the start </code? It should be simple, but there are a couple of small complications to overcome. The first complication is that the electro-mechanical nature of the wallbox creates occasional voltage spikes (from relays etc.) that are not part of a pulse train. For example a short spike is created when an inserted coin generates a credit, and another spike is seen when the rotor arm starts to move. These could lead to decoding errors if they are interpreted as part of the pulse train. The software needs to identify and ignore these. The second complication is that the square waves of the pulses (from the voltage regulator) have short gaps between them when the GPIO will read False. We need to ignore these also. To solve both of these issues, I wrote a short function that allowed the logic to ignore short gaps and spikes. It checks that a “change of state” from true to false or vice versa lasts a certain minimum amount of time. Ideally this would have been based on the RPi timer, but I found that the latency of using the time object introduced too much error and in the end I opted for a simple iterating loop and the right number of loops was found by trial and error. It's not pretty and I would welcome any other ideas from people about this. Possibly rewriting in a compiled language would allow us to do this more elegantly. There is a full list of the actual Python code on the resources page. The logic now runs like this: <code> Wait for a pulse When a pulse is detected, check that it is long enough to be a proper pulse, if it is very short ignore it If it is a proper pulse then start counting the pulse train and don’t stop until there has been a gap of more than X seconds Whenever the GPIO pin state changes check that the change is long enough to correspond to a signal pulse or gap. If it is very short ignore it Count the pulses before the big gap and store this as the selection number Then count the pulses after the big gap and store this as the selection letter Play the song corresponding to that selection Go back to the start
I wrote several debugging programs which output information to the terminal to show data about time between pulses and final calculation of the selection so that I could be sure that it was working and to fine tune the function that ignores the spikes and short gaps. Here is a video showing one of these programs working.
Now we have to get the Raspberry Pi to communicate with the home music network. In my case this was a SONOS system, but this approach will work with any home music network that supports the open UPnP protocol.
The approach will work equally well with a wired Ethernet connection, but my house is not cabled and I wanted to make the installation as clean as possible. If you are going to use wi-fi then the first thing to do is to get your RPi up and running and connected to your home network through a wireless USB dongle. The dongle I used is listed on the resources page. I googled the RPi forums to get some instruction. Try this link if you are having trouble getting it to work. There is also lots of good advice on the Internet about how to use the APIs of UPnP so I won’t go into the details here. I got a great start from a blog called travelmarx (thank you!).
I have written the code in Python 2.7, so you may need to change some syntax to get it running in Python 3. If you are planning to connect your RPi to a SONOS then you'll be able to use all my Python code as it is - you only have to change the IP address of the player (which you can find from your SONOS controller).
To add a song to the queue you simply have to call the right API and pass it the right arguments. In my case this API call looks like this:
webservice = httplib.HTTP("192.168.1.70:1400") webservice.putrequest("POST", "/MediaRenderer/AVTransport/Control") webservice.putheader("Host", "192.168.1.70:1400") webservice.putheader("User-Agent", "Python post") webservice.putheader("Content-type", "text/xml; charset=\"UTF-8\"") webservice.putheader("Content-length", "%d" % len(SoapMessage)) webservice.putheader("SOAPAction", "urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue") webservice.endheaders() webservice.send(SoapMessage)
Where SoapMessage text field is as follows (with the selected track substituted for %s):
SOAP_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope SOAP-ENV:encoding xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Body> <ns1:AddURIToQueue xmlns:ns1="urn:schemas-upnp-org:service:AVTransport:1"> <InstanceID>0</InstanceID> <EnqueuedURI>x-file-cifs://Rainbow-Media/QRecordings/jukebox/%s.mp3</EnqueuedURI> <EnqueuedURIMetaData></EnqueuedURIMetaData> <DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued> <EnqueueAsNext>1</EnqueueAsNext> </ns1:AddURIToQueue> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
I was keen to get it up and running, so I haven't added anything clever to find the SONOS address dynamically, I just hard coded the IP address of the SONOS player that I wanted to control. You can find the IP address from the SONOS player in the help menu “About my SONOS system”. The downside of course is that this IP address might change in the future if you restart the system or add different players.
If you read the literature online about using UPnP it will give the impression that you have to pass the metadata of the song with the API. The metadata provides the home media network with lots of contextual information about the song; the song title, the album it comes from, the genre, etc. This information is then available to be displayed by the media player. I found that constructing the XML metadata was complex, the API is very sensitive to any syntax errors and provides zero help with debugging. In frustration I simply tried it without the metadata and it worked fine. Even better, when the SONOS system indexes your music library it stores all the metadata (which is also usually embedded in the music file), so even when you simply pass it a file name, all the usual song information is displayed in the player.
An additional advantage of this is that the file name can be completely different from the song title and allows us to have a dedicated jukebox folder on the NAS, with 200 songs named by their selection letter and number. For example rather than little_red_corvette.mp3 - we can have C1.mp3. The great thing about this is that there is no requirement to have any kind of name look up from the selection to the file name. To change a song in the jukebox you simply copy the song file to the jukebox folder and rename it to the selection that you place the title strip in.
Final touches I have wrapped the small circuit board in a heat shrink wrapper and housed the RPI in an adafruit box and tested it working inside the jukebox. Here is a video showing it working. The final touch will be to get the RPi to launch the controller program automatically at startup (next week maybe!).
Further Enhancements Since the RPi is kept inside the wall box (and therefore there is no display attached) I would like to add some simple visible output to highlight common problems that might occur that may need fixing e.g. Unable to connect to wifi network, unable to connect to Sonos, song file not found. I will probably do this with a simple array of 4 LEDs that have some binary index of common faults - inspired by DELL desktops!
I would also like to add “dynamic” selections to the jukebox. For example you could print a title strip “song of the week”, or “party playlist” and tell the SONOS to play the most popular song that week, or a pre-configured playlist.
You could also have title strips with radio stations on them - and for an avid Radio 4 listener like me this would be perfect.