Running the AWS/IoT-Script as Service – Quick and Dirty

In our last post we showed a python script which reads data from an ELM327 compatible OBD interface and a GPS mouse on a Raspberry PI and sends it to the AWS IoT Core. Disadvantage is that the script has to be started manually and in a vehicle you usually don’t have a keyboard and monitor and also no time to start scripts before driving off. So it would be more practical if the Raspberry PI automatically starts and connects to AWS IoT Core after the vehicle is started. A normal raspbian installation also requires to be shut down before the Raspberry PI is switched off else you’ll destroy the SD card. Using the Read Only Raspbian presented by adafruit you can also get rid of that problem.

Hardware and Software in use

Preparing the script for the usage as service

Linux is using the “init” system to configure the system during startup and to start background services [1]. Raspbian is using the “Systemd” init system as many other recent linux distributions [2]. To start the python script as a service, we need two things: A shell script which is starting the python script using the right command line arguments (endpoint, certificate etc.) and a Systemd configuration file which is running the script during startup. The python script has to be run as user, e.g. “pi”, for the required python modules to load. In the last post you got all required files from github and AWS. First we have to put all the files required for the service, which are the python script, the Amazon root certificate and the certificates for the thing, into a directory where the user has access. Therefore, we create the directory /usr/local/lib/simple-car-connect and copy all files into it (XXX is the ID of your thing):

pi@raspberrypi:~ $ ls -l /usr/local/lib/simple-car-connect/
total 36
-rw-r--r-- 1 root root  1220 Oct  4 20:31 XXXX-certificate.pem.crt
-rw-r--r-- 1 root root  1675 Oct  4 20:31 XXXX-private.pem.key
-rw-r--r-- 1 root root   451 Oct  4 20:31 XXXX-public.pem.key
-rw-r--r-- 1 root root  1187 Oct  4 20:31 AmazonRootCA1.pem
-rw-r--r-- 1 root root   655 Oct  4 20:31 AmazonRootCA3.pem
-rwxr-xr-x 1 root root 13006 Oct  4 20:31 simple_car_connect.py

We can not give any arguments to the python script from the Systemd config file, so we need a shell script starting the python script with the required arguments. In principle it’s the full command line from the last post. Additionally there is a delay of 10s so that the system has time to initialize during startup (e.g. to set the correct time using NTP). Create the script in /usr/sbin/simple-car-connect.sh with the following contents:

#!/bin/sh
sleep 10
python3 /usr/local/lib/simple-car-connect/simple_car_connect.py --endpoint YYYY-ats.iot.eu-central-1.amazonaws.com --root-ca /usr/local/lib/simple-car-connect/AmazonRootCA1.pem --cert /usr/local/lib/simple-car-connect/XXXX-certificate.pem.crt --key /usr/local/lib/simple-car-connect/XXXX-private.pem.key --topic cars/01 --count=0 > /tmp/simple_car_connect.log 2>&1

The first line contains the “shebang” which runs the script using /bin/sh. In the next line it’s waiting for 10s before the python script is started with the required arguments. I added the delay because NTP wasn’t always ready when the script tried to connect to AWS IoT Core. This leads to failed connections attemps which aren’t further handled here. YYYY is the AWS IoT Core endpoint and XXXX the thing. Using output redirection standard and error output (the 2>&1 part) are redirected to the file /tmp/simple_car_connect.log. This directory is in a directory writable by the user.

Systemd configuration

In the directory /etc/systemd you can find a structure of subdirectories, files and links which are responsible for starting different background services during raspbian boot. You can find more about it e.g. in the “Kofler” [2] (unfortunately it’s a german book – i’ll look up an english reference). Here i’ll just shortly describe what you have to do so that the python script sending OBD data to AWS IoT Core is automaticall started during raspbian boot. First you have to create a config file in /etc/systemd/system (i called it cariot.service here):

sudo nano /etc/systemd/system/cariot.service

The file has the following content:

[Unit]
Description=Simple Service for sending ELM327 OBD Data to AWS IoT Core
After=network.target ntp.service

[Service]
User=pi
ExecStart=/usr/sbin/simple-car-connect.sh

[Install]
WantedBy=multi-user.target

In the section [Unit], after a short description of the service we list the services which have to be started before our service. We need “network.target” and “ntp” , so that the network connection is ready to connect to the AWS IoT Core and additionally we need a synchronized system time.

Next, in [Service] we define which command is used to start our service. Here we use the shell script we created in the last section. Additionally, we have to run it as user “pi”. This increases safety and also not all python modules which we use are running as root.

Using the command “service” the script be started manually, e.g. for testing:

pi@raspberrypi:~ $ service cariot start
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to start 'cariot.service'.
Authenticating as: ,,, (pi)
Password: 
==== AUTHENTICATION COMPLETE ===

Similarly, you can use “service cariot stop” to shut down the service.

Prepare NTP for read only raspbian

After running the “read only raspbian” script the NTP daemon is not synchronizing the system time any longer. The problem description and a solution which was working for me is described on stackexchange. Here is a summary of the most important steps. For detailed description, please look up in the original post. First you have to install the required packages, then copy the config file to the right place and finally open the config file of the NTP service for editing:

pi@raspberrypi:~ $ sudo apt-get install ntp ntpdate
pi@raspberrypi:~ $ cp /lib/systemd/system/ntp.service /etc/systemd/system/ntp.service
pi@raspberrypi:~ $ sudo nano /etc/systemd/system/ntp.service

In the NTP config file the “PrivateTmp” feature has to be deactivated by commenting out the line “PrivateTmp=true. The file then looks like following:

[Unit]
Description=Network Time Service
Documentation=man:ntpd(8)
After=network.target
Conflicts=systemd-timesyncd.service

[Service]
Type=forking
# Debian uses a shell wrapper to process /etc/default/ntp
# and select DHCP-provided NTP servers if available
ExecStart=/usr/lib/ntp/ntp-systemd-wrapper
#PrivateTmp=true

[Install]
WantedBy=multi-user.target

Finally we have to mount the directory /var/lib/ntp as tmpfs. Therefore, the file /etc/fstab has to be opened:

pi@raspberrypi:~ $ sudo nano /etc/fstab

And the entry “tmpfs /var/lib/ntp tmpfs nosuid,nodev 0 0” has to be added. The file then looks like following:

proc            /proc           proc    defaults          0       0
PARTUUID=4520e3fc-01  /boot           vfat    defaults,ro          0       2
PARTUUID=4520e3fc-02  /               ext4    defaults,noatime,ro  0       1
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that
tmpfs /var/log tmpfs nodev,nosuid 0 0
tmpfs /var/tmp tmpfs nodev,nosuid 0 0
tmpfs /tmp tmpfs nodev,nosuid 0 0
tmpfs /var/lib/ntp tmpfs nosuid,nodev 0 0

Setting a fixed IP address for ethernet

For the ethernet interface of the Raspberry PI a set a fixed IP address. Before setting the fixed IP, i had some trouble connection to AWS. For the debugging in the vehicle the fixed IP is handy because you can just e.g. set a matching IP on your notebook and connect to the Raspberry PI. Usually you don’t have a DHCP server there. After setting the fixed IP, the connection problems disappeared. Probably, NTP or some other network service was confused during waiting for DHCP to configure the ethernet interface. To set a fixed IP address, you have to edit the file /etc/dhcpcd.conf. Because we need the interface only for connecting to a notebook it’s enough to just give the IP. Router and DNS server are not configures. The connection to the internet anyway is done using the WiFi interface configured in the last post. You can open the fils using:

pi@raspberrypi:~ $ sudo nano /etc/dhcpcd.conf

For the interface eth0 you comment out all existing lines. Just the two following lines have to be there:

# Example static IP configuration:
interface eth0
static ip_address=192.168.188.200/24

If you use an IP out of the range of your router (which obviously shouldn’t be used by another device already), you can also easily connect to the Raspberry PI at home just by connecting the ethernet interface to your router. In the vehicle, you have to set a fixed IP for the notebook which has to be in the same segment as the Raspberry PI, because there is no DHCP server. You can use e.g. the 192.168.188.201.

Configuring the SD card as “read only”

Finally, we execute the adafruit-Script to set up the SD card as read only. You can find a description of the script at the adafruit site. Using the following two commands you can download and execute the script:

pi@raspberrypi:~ $ wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/read-only-fs.sh
pi@raspberrypi:~ $ sudo bash read-only-fs.sh

The question if you want to change the file system to read only, of course you have to answer yes (“y”). After that, it asks for some more features (which are described in the Adafruit post). In our configuration, all features are deactivated but it shouldn’t harm if you activate them.

After the script was executed, the Raspberry PI should survive being switched without shutting down without damaging the file system on the SD card. The downside is that you can not easily modify files or install or update packages. But by remounting the file system as read-write after logging in you can do everything as used until the next reboot. Therefore you just have to enter the following command:

[1] M. Kofler: Linux, 14. Auflage, Rheinwerk Verlag Bonn, 2016, S. 905 ff.

[2] M. Kofler, Linux, 14. Auflage, Rheinwerk Verlag Bonn, 2016, S. 920 ff.