Build a Raspberry Pi Vehicle Interior Monitor – Running Code at Startup
In this blog series I'm documenting my maker journey as I build a Raspberry Pi-based vehicle interior monitor (PiVIM). This is the full list of posts in the series:
- Build a Raspberry Pi Vehicle Interior Monitor—Overview
- Build a Raspberry Pi Vehicle Interior Monitor—Screen Test
- Build a Raspberry Pi Vehicle Interior Monitor—Mobile Broadband
- Build a Raspberry Pi Vehicle Interior Monitor—Temperature Monitoring
- Build a Raspberry Pi Vehicle Interior Monitor—Running Code at Startup (this post)
- Build a Raspberry Pi Vehicle Interior Monitor—Putting it All Together (coming soon)
Originally I'd assumed that the topic of this blog—running code at startup—was so straightforward that it wouldn't warrant a post of its own. However I ran in to a nasty gotcha, the fix for which might help somebody else, and I also learned a continuous delivery lesson (I'm slightly ashamed to say, given that much of what I blog about is continuous delivery) which is worth explaining.
When it comes to running code or programs at startup on Linux there are several ways to tackle the problem—see this useful Dexter Industries post which describes five different methods. I had no prior experience of this, and since I'd seen quite a few forum posts advising using rc.local and it was the first in the Dexter Industries list of five I started with that. The starting point is to edit the file:
1 |
sudo nano /etc/rc.local |
and then add this command before the exit 0 line:
1 |
sudo python3 /home/pi/PiVIM-py/my_pivim.py 9sMlkCJ9gvchc6VMCC3U0bSmixyzSYP7 & |
In case you were wondering, the long alphanumeric being passed in to my_pivim.py is the (obscured) access key for my InitialState streaming analytics account, and the ampersand at the end ‘forks the process' so the Raspberry Pi can continue booting. Anyway, quick reboot and sit back to watch my code spring in to life...
Or not since nothing happened. Check the syntax and try again—nothing. So I tried running the command from a prompt and ran in to this error:
1 2 3 4 5 |
File "/home/pi/PiVIM-py/my_pivim.py", line 5, in <module> from pivim import mobile_broadband as mb File "/home/pi/PiVIM-py/pivim/mobile_broadband.py", line 12, in <module> import requests ImportError: No module named 'requests' |
So the requests module can't be found. Very weird since the shortened version of the syntax works absolutely fine from the command line in the PiVIM-py directory:
1 |
sudo python3 my_pivim.py 9sMlkCJ9gvchc6VMCC3U0bSmixyzSYP7 & |
Next stop was to try without sudo, since everything in rc.local runs (apparently) as root anyway. The result of that was that the command didn't run in rc.local but did run from a command prompt. So, some sort of permissions thing, but was the problem limited to just the requests module? To cut a long story short I found out that it was just the requests module that was causing the problem, which led to installing requests again but ultimately no joy. And Google searches didn't turn up anything useful at that stage either.
I decided to abandon rc.local and try systemd instead based on the recommendation of the Dexter Industries post. My initial unit file (as /lib/systemd/system/pivim.service) looked like this:
1 2 3 4 5 6 7 8 9 10 |
[Unit] Description=PiVIM After=multi-user.target [Service] Type=idle ExecStart=/usr/bin/python3 /home/pi/PiVIM-py/my_pivim.py 9sMlkCJ9gvchc6VMCC3U0bSmixyzSYP7 [Install] WantedBy=multi-user.target |
With the permissions set on the file and the service enabled I rebooted...to nothing. Fortunately, I was able to see what the service was doing using this command:
1 |
systemctl status pivim.service |
This resulted in some useful, albeit frustrating output:
1 2 3 4 5 6 7 8 9 10 |
Nov 21 21:40:01 pivim systemd[1]: Started PiVIM. Nov 21 21:40:02 pivim python3[454]: Traceback (most recent call last): Nov 21 21:40:02 pivim python3[454]: File "/home/pi/PiVIM-py/my_pivim.py", line 5, in <module> Nov 21 21:40:02 pivim python3[454]: from pivim import mobile_broadband as mb Nov 21 21:40:02 pivim python3[454]: File "/home/pi/PiVIM-py/pivim/mobile_broadband.py", line 12, in Nov 21 21:40:02 pivim python3[454]: import requests Nov 21 21:40:02 pivim python3[454]: ImportError: No module named 'requests' Nov 21 21:40:02 pivim systemd[1]: pivim.service: Main process exited, code=exited, status=1/FAILURE Nov 21 21:40:02 pivim systemd[1]: pivim.service: Unit entered failed state. Nov 21 21:40:02 pivim systemd[1]: pivim.service: Failed with result 'exit-code'. |
Back to the same old problem. However, this time a Google search with systemd as part of the query came up trumps with this StackOverflow question where a respondent had the same problem as me. The answer was simple: add a User=pi line beneath the [Service] section.
With this change PiVIM's control panel sprang in to life on startup—as I had expected it to several hours earlier:
Why the requests module alone caused this issue I didn't manage to find out and probably never will. On the plus side I now know how useful systemd is, not least because of the range of useful commands that work with it (see here and here for useful guides).
And so to the moral of this story, which is that if you don't practice continuous delivery—ie if you are not frequently pushing your code to ‘production'—you are likely to get bitten by problems that only surface towards the end of your development effort. This wasn't too much of an issue for me at home but in a professional setting not practicing some form of continuous delivery causes major headaches for developers all the time. Even if you don't develop professionally you might get caught out at a fun event such as a hackathon, so it's definitely a habit worth trying to adopt.
Cheers—Graham