I own two Roku/Pinnacle Soundbridges. And I am frightened for the day they cease existence. As a matter of fact I am trying to buy some used ones, just as spares. These little tubes are the best internet radios around, even nowadays. Plug them in, configure the network and voilá, you have a running radio that is accessible via a webinterface. Back in these days no marketing chef or product designer even thought about forcing the buyers to register with their mailadress, birthdate, creditcard numbers, etc.
The Soundbridge has an open telnet port that can be used to configure and control it. So it’s the perfect device for tinkering around and I am going to use the display to show information sent from the Raspberry.
For a start it would be cool to have the actual weather forecast visible. The Soundbridge is located in the kitchen and usually the second thing that is switched on in the morning. Sounds easy. Just get the weather somehow, telnet to the Soundbridge and display the weather. Which, technically would just be something like this:
macmini:~ th$ telnet xxx.xxx.xxx.xxx 4444 Trying xxx.xxx.xxx.xxx... Connected to soundbridge Escape character is '^]'. Welcome to the SoundBridge Shell version 3.0.44 Release Type '?' for help or 'help ' for help on . SoundBridge> sketch welcome to sketch (? for help) sketch> text 0 0 "This is the message!" sketch> quit SoundBridge> exit Connection closed by foreign host. macmini:~ th$
Pretty straightforward and working. The Soundbridge exposes a shell from which we go to the “sketch” subshell. There we can write to the display. Hooray. Pretty fast prototyping. And it looks like this:
Way too easy!
Unfortunately, as with every other software development project, things get a little more complicated once it has started. And one begins to think seriously about what has to be done.
- When the telnet connection is closed, the display resets to its idle state, so the message is gone
- As long as the sketch-subshell is active the Soundbridge does not respond to its remote
So in fact I can have the weather displayed but then no longer listen to the radio. Not good. Furthermore I want to keep the load on the Raspberry as low as possible, so I need a non-blocking solution and cron is out of the question.
The Soundbridge shell has some more features that come in handy. It is possible to catch the IR-commands it receives via the shell. So problem number 2 is easily solved: Get in “catch”-mode and wait for a command from the remote, then free the display and let the Soundbridge respond to commands. But this would mean having the program cycle forever until a command is received.
I wanted to use this project to learn programming in Python so I decided against using threads. They would have been the solution to the above problems. Have one thread listen for IR-commands and signal to the display-thread when to free the Soundbridge.
Unix system have another great way of doing things in the background: daemons. So having to decide between threads and daemons I went for the daemons. A little bit of research led me to a great tutorial and sample code. The author states that it is in the public domain and may be used, so I did.
I came up with a solution using two scripts. One very small script is responsible for only waiting for an IR-command and then free the display, I am going to call it the Listener. The second, larger, script will grab the weather, display it and then wait an hour and start over again, the Writer. This is only possible because the Soundbridge does not care from which telnet connection it receives the “quit from sketch subshell” command.
As there is no signalling between the two scripts there is one desirable side effect. The scripts are never terminated. When the Listener receives an IR-command it frees the display and one may use the Soundbridge as a radio. The Writer continues running, so after an hour, even when listening to the radio, an updated weather information is displayed. Cool.
So here’s the Listener:
#!/usr/bin/env python import sys, time, telnetlib from daemon import Daemon class SBListener(Daemon): def run(self): """ open telnet port to soundbridge start echoing of IR commands wait for user pressing SELECT-button on remote go into sketch subshell quit out of sketch subshell to release display """ port = 4444 tn = telnetlib.Telnet("soundbridge_ip_here", port) while True: # run forever tn.read_until("SoundBridge> ") # wait for prompt from soundbridge tn.write("irman echo\n") # issue command for IR intercept tn.read_until("irman: CK_SELECT") # wait for prompt on received IR tn.write("sketch\n") # use this session to enter sketch subshell tn.read_until("sketch> ") # wait for prompt tn.write("quit\n") # exit sketch subshell, clears display tn.write("\n") if __name__ == "__main__": daemon = SBListener('/tmp/sblistener.pid') if len(sys.argv) == 2: if 'start' == sys.argv: daemon.start() elif 'stop' == sys.argv: daemon.stop() elif 'restart' == sys.argv: daemon.restart() else: print "Unknown command" sys.exit(2) sys.exit(0) else: print "usage: %s start|stop|restart" % sys.argv sys.exit(2)
This is the Writer:
#!/usr/bin/python ''' Writing weather data to a Roku Soundbridge. Version 0.1a ''' import sys, time, telnetlib from daemon import Daemon from YahooWeather import YahooWeather # one of my packages class SBWriter(Daemon): def run(self): try: myWeather = YahooWeather("yahoo_WOEID_here", "c") # initiate weather object port = 4444 tn = telnetlib.Telnet("soundbridge_ip_here", port) sleeptime = 3600 # update interval is 3600 seconds while True: # forever tn.write("\n") tn.read_until("SoundBridge> ") # wait for prompt tn.write("sketch\n") # enter sketch subshell tn.read_until("sketch> ") # wait for prompt tn.write("encoding utf8\n") # set display encoding tn.read_until("sketch> ") # wait for prompt tn.write("font 1\n") # select font tn.read_until("sketch> ") ''' the next two lines write the weather forecast to the display the methods they invoke on the myWeather-object return preformatted strings parameters after text command: c is centered x-axis, 0/9 is y-axis ''' tn.write("text c 0 " + myWeather.getSoundbridgeOneLinerNow().encode('utf-8')) tn.write("text c 9 " + myWeather.getSoundbridgeOneLinerFCast().encode('utf-8')) time.sleep(sleeptime) # wait some time myWeather.populate() # repopulate weather object sleeptime = 3600 # everything was OK, next read in 60 minutes tn.write("quit\n") # exit sketch subshell except: ''' if anything goes wrong the sleeptime is reduced to 10 minutes so maybe we get updated weather next time ''' sleeptime = 600 if __name__ == "__main__": daemon = SBKitchenWriter('/tmp/sbkitchenwriter.pid') if len(sys.argv) == 2: if 'start' == sys.argv: daemon.start() elif 'stop' == sys.argv: daemon.stop() elif 'restart' == sys.argv: daemon.restart() else: print "Unknown command" sys.exit(2) sys.exit(0) else: print "usage: %s start|stop|restart" % sys.argv sys.exit(2)
So this works great. I have refined my YahooWeather (which I wrote for learning purposes, too) class a little bit. Now between 0:00am and 5:00pm the first line displays the current weather and the forecast for today. After 5pm the actual weather and a forecast for the next day is displayed.
The image on top of the page, the “featured image” is an actual photograph of what is displayed. In German, OK. The format is :
actual temperature (time of update), Today/Tomorrow: high/low – textual weather condition
next three days with high/low
Thank you for reading the whole long thing. These are my first steps with Python. So feel free to comment on any quirks or things that could be done more elegant in Python. Or, if something is just quite plain wrong.