Microservice for Raspberry PI GPIO control
Once I had connected my Raspberry Pi to the LAN network, and the heating system was driven by a Raspberry Pi's GPIO, the way to turn it on/off was by logging into the Pi through ssh, and manually executing the Python control scripts from the command line which was kind of inconvenient, I would tend to centralize the functionality on the control web application, but this seems not quite as modular as I would like it to be so I was thinking on alternatives.
Wouldn't it be nice that the Pi itself could offer an interface for controlling the heating system and also could provide access to the heating system logs using a standard channel so any other component could consume it? This seemed like the natural path to go for me at the moment so I started looking for ways to implement it. I thought the right solution would be to have a small REST server on the Pi. As I had already wrote the heating system control scripts on Python and it is a language I like, I decided I could look for the smallest Python HTTP server I could find, and found flask.
At first I decided to install flask by doing apt-get install python3-flask
because it seemed an easy and reliable way to do it. The server must run with the host
parameter set to 0.0.0.0
in order to allow other machines to connect to the server running the Pi.
I started defining simple scripts and setting them on routes, this was pretty straightforward as these scripts wouldn't be longer than 10 lines each. I could successfully query the log file from the outside.
I did the same for the turn_on and turn_off routes and it was working as expected BUT at this point I started thinking on deployment, one think I wanted to was to track de dependencies and make them easy to install on a future deployment process, I started investigating and found this pattern in the official flask documentation for organizing larger applications, this architecture made much more sense to me so I started reorganizing what I had. The first step would be to get rid of flask from apt-get and then install python3-pip
and let pip3 manage all dependencies, including flask itself.
For letting pip install the dependencies I created a setup.py
file following the example on the documentation linked above and placed it in the project root, then executed pip3 install -e
.
After everything was installed I couldn't execute the flask command, of course that was because the python bin folder /home/pi/.local/bin
was not added to my $PATH
so after doing that I came back to the point I was before when I could execute flask manually.
I was almost there, I could run the micro-framework manually and it worked as expected, the only thing that was left was to install a lightweight web server on the Pi that would automatically run when booting the Pi and connect this Python application to the outside. I picked Lighttpd for two reasons, because I had never tried it and because it was stated as being very lightweight, also combined with FastCGI of course for the advantages listed on the linked article. Luckily there is a nice article on the official flask documentation on how to do so. And I also took some advice from this question on stack overflow.
Summarizing what I did was to add flup to the setup.py
file as a dependency, updated dependencies, then installed lighttpd doing apt-get install lighttpd
, then enabled the modules indicated in the documentation by doing lighttpd-enable-mod alias
, lighttpd-enable-mod fastcgi
and lighttpd-enable-mod rewrite
and configured the service to start on boot by doing systemctl enable lighttpd
. Then restarted the service by doing systemctl restart lighttpd
and checked it was working by accessing to the Pi's IP address on port 80.
At this point I was almost almost there, I wrote as a Python3 executable the FastCGI file, and appended the site configuration to lighthttpd.conf
. I was ready to celebrate when I accessed the application path and... 500 server error directly to my face. I spent like an hour trying to debug what was going on, I checked the configuration files over and over, I read all the linked tutorials again and again, I created a simpler test.py
FastCGI file, nothing, executed it from the command line and everything seemed to be working but when accessing the site, nothing. All I could read was that the FastCGI process was failing on the lighttpd error.log
file but... Why? The script was super simple and I had already manually executed it a thousand times! I was almost drowning in despair when like the 20th time I manually executed the script failed. What? How? I have executed it 19 times before and it worked, then I realized I was logged as the root user. How can the user pi run the script the the root user not? Took a look at the error, No module named 'flup'
. That was it!! The dependencies installed by pip3 are not system but user wide, and as lighttpd executes it's code as www-data so that user had no modules installed. I double checked it by manually running the FastCGI script but as the lighttpd user.
I then moved the code to /var/www/
and prepared for installing the pip3 dependencies wasn't as easy as doing a sudo -u pip install
and required another search on stack overflow as .local
and .cache
folders needed to be created and then the pip install executed with these paremeters:
sudo -H -u www-data pip3 install -e .
Finally after rebooting I could read the Pi logs on my web browser, there was just a little last hurdle to jump, when calling the endpoints that actually manipulate the GPIO another 500 error was returned, and I could read this message:
No access to /dev/mem. Try running as root!
But nothing could stop me then so I ignored the error message recommendation and just gave www-data permission to manipulate the GPIO by adding it to the Unix group by doing:
sudo adduser www-data gpio
And... that was it, finally the API was up, responding as expected and nothing else needed to be done for now. I just finally rebooted the Pi to check that the web server is starting by default and it will not go down if the Pi reboots if a power outage happens or some other reason.
I have finished this for now as it covers all my needs, there is still room for improvement but I think it is enough for this article. The API can still (and should depending on your architecture) be secured and also I would like to spent some time investigating on automated deployments and configurations. I hope you found this interesting or useful, and if you are trying to implement the same I hope by reading this I could save some of your time!
I uploaded the final code to github so you can check it here.