The next stage of the process was probably the most challenges part. As always with software, its starts off easy, and gradually becomes more and more complex as you begin to solve problems. I’ve written this post in a way that highlights the main challenges I faced and how I went about solving them.
Without any further discussion lets begin.
Problem 1 - Using NIM
The first problem came up at the very, very start of the implementation. The original idea was to use this project to learn the NIM programming language. I have very little experience with using it currently so thought this project was perfect to learn about it.
It soon became evident that learning NIM and finishing this project would be a massive undertaking (especially as using it in my proffered language took long enough anyway). The fact that I would need to learn the inner working of a statically typed compiled language to complete this was a bit too much. Whilst it would make the deployment much easier, I realised the best option is to use my preferred language….Python 🐍.
Python enabled me to quickly iterate and use the language and libraries that I am familiar with to quickly develop this element.
SOLUTION: Use Python 🐍
Problem 2 - Development Environment
Now that Python was selected as the language to use, the next question was where to develop. The original plan was to SSH onto the RaspberryPi itself and develop there.
Within about 5 minutes of testing this, I realised it was not a viable option that could be considered, given the slow nature of the RPi, using it as a development environment was out of the question (I don’t understand how people do it)
Instead the solution was a simple one, develop the laptop in a known environment and deploy (another headache waiting to happen), onto the Pi at a later point.
SOLUTION: Alternative development environment
Problem 3 - Interacting with the Frame
The next problem was my own doing. I was heavily focused on keeping the RAM overhead as low as possible that I embarked on a mission to not use X as a display system. Instead my first thought was, much like terminals and such, I would try to write directly to the Frame Buffer.
This was obviously…stupid. Manipulating a raw frame buffer is not a sensible solution at all.
After about a week of week of research and messing about, I ultimately ended up just installing an X server, and utilising
feh to display a static image on the screen. This didn’t run up the RAM usage at all. This setup enabled me to develop much faster, but also be able to easily develop and test this locally on my laptop.
SOLUTION: Use Xorg and FEH
Problem 4 - Configuration
Solving the first 3 problems enabled development to proceed at a very rapid pace, and I was quickly able to integrate with Spotify, grab the image and display it on the screen.
Eventually I started to build out some more features, such as using the
Pillow library to show the name of the artist on the image. The problem with this is I wanted to be able to configure this on and off.
As the main program runs on an infinite loop constantly querying spotify, I needed a way to interact with the application.
My original thought was I would play certain songs to manipulate the image, and I would store this in a playlist. In theory this sounded good, but a bit of a rubbish solution practically.
I then thought the most sensible option would be to spin up a webserver, and allow the the user to update configurations from the webserver. This was a mch better option that presented its own mini challenges, some of which I listed below.
- As the main application run on a continuous blocking loop, it was blocking the webserver which also runs in a loop. I managed to solve this quite easily using the python
threadinglibrary to run 2 simultaneous threads, one for spotify and one for the webserver.
- The webserver required something simple to host a few small pages. Rather than using Flask, I decided to try Bottle, mainly because it was very very small, but contains everything I needed to support a stripped back website. It even has HTML templating support built in!
- Configuration need to be shared between these threads, as its updated by the webserver and used by my Spotify loop. The official way to support this is to use some features of the threading library to ensure no race conditions occur. However I ended up simply creating a config singleton class and passing that around. It worked well enough
- Spotify login follows a standard OAUTH2 pattern. The complication here was that login process redirects you back to a domain to complete the login, however, as my application only run internally on the networks, it only is accessible by internal addresses. I solved this issue by allowing the redirect back to localhost (which wont load), and then I manually copy the URL into a form field to extract the data. Again, it worked well enough.
SOLUTION: Configure the application using a local webserver
Problem 5 - Deployment + Upgrades
Deployments. Are. A. Nightmare.
As we all know building applications is easy. Deployment are the hardest part, and this is no exceptions. As mentioned earlier I was developing a python application when needed to be deployed onto the Pi. To complicate matters even more, I wanted an approach that will automatically update any code from the GitHub, without any manual interactions, preferably on a reboot.
Generally I would create a docker image with all the relevant library’s and code, and deploy that onto the Pi, however I didn’t want the massive overhead of installing docker. Therefore I needed a different approach.
Initially I decided to download the relevant packages, zip them up and store them in the Github on “build”, and then unzipped on the Pi. Turns out this didn’t work, as Pillow is compiled for a different CPU architecture. After a while of trying to resolve the problem, I gave up and decided to do it in the simplest way.
In terms of deployment/upgrade and running the following process is carried out on each boot:
- Pi is booted and will auto-login, and automatically run
startxto start the X server
- The latest version of the code from GitHub is pulled and merged
pipwill install the packages using a cache
- Python will start the application and show a loading image
- Application will log into Spotify, pull the current song image and display it on a loop.
This isn’t the most optimised process, in any way, it requires a full rebuild ever time its turned on. The whole process takes approximately 2 minutes. This can be reduced, but given that the frame isn’t turned on and off repeatedly, I call this acceptable.
SOLUTION: No good solution - continue with unoptimised version
I am really happy with the the first version of this application. Overall the development took about 10 hours, spread over a week, and enabled me to learn quite a few new coding concepts.
- The plan is still on the table to rewrite in NIM. I think it will be a long and slow burn project, hopefully the deployment issues, threading hacks, and reliance on other application such as
fehand others may be removed.
- Currently the same image is re-downloaded every couple of seconds, this could heavily be optimised, to ensure we cache the image unless the song changes.
- I plan to add further styling to the returned image, such as converting the image to Black and White.