This guide explains how to control your car in real-time and dynamically update the PID parameters as the car runs on the track from inside a Jupyter notebook. To achieve that, we will configure Jupyter to start automatically at the boot, install ipywidgets which would allow us to interact in real-time with the Jupyter program, and configure the PWM to be controlled without sudo such that we can control the car speed and the servo angle within the notebook.
Important, we will use Jupyter notebook and not lab as we did before. Both programs have the same functionality, however, lab is under active development and it is difficult to find a version of widgets that will work with the current Jupyter lab version. Since widgets are an important part of this tutorial we will use Jupyter notebook instead of lab.
In Linux, as soon as the kernel gains control over the CPU, it starts the init program that start all other services required by the operating system to function properly. Although, there are multiple programs that do the init functionality, the majority of the current Linux distributions, including Raspbian, are running systemd. Systemd is configured using text files that indicate what program to run and on what other services it depends. In our case, we will create a systemd service to start Jupyter notebook at the end of the boot process so you can work on your program without the need to ssh first and start the Jupyter notebook.
Step 1. Set Jupyter password
By default, Jupyter uses tokens to authenticate the users. When Jupyter starts, it generates a random sequence that is printed in the console, and asks you to copy-paste it into the browser such that it knows that you are the one who started the program. Since we will start it as a services, we would not have access to the sequence, as it will be printed into a log file. To make it work, we should enable password authentication in Jupyter which will disable the token authentication. To set the Jupyter password, first activate raceon environment by running workon raceon
, then create the notebook configuration file where the encrypted password will be stored jupyter notebook --generate-config
and set the password using jupyter notebook password
that will ask for the password and update the configuration.
Step 2. Create a startup script that activates raceon environment and starts Jupyter server such that we can run it using systemd automatically at boot. In the home folder of user pi, use nano, a command line text editor, to create this script file. Type nano startjupyter.sh
and paste the following content:
#!/bin/bash
source /home/pi/.virtualenvs/raceon/bin/activate
jupyter notebook --ip 0.0.0.0 --port 8888
Save the file by pressing Ctrl+O then Enter and exit using Ctrl+X.
Now, make the script executable using chmod +x startjupyter.sh
and run it to check that the Jupyter notebook starts by typing ./startjupyter.sh
in the terminal and then open in browser http://raspberrypi-ID:8888/ where ID is your pi number. Stop the script by pressing twice Ctrl+C
Step 3. Create the systemd configuration file that will instruct systemd to run the startjupyter.sh
script as the pi user after Linux is ready to accepts user logins. Use nano to create the configuration file sudo nano /lib/systemd/system/raceon-jupyter.service
and paste the following content:
[Unit]
Description=Service to start jupyter automatically
After=multi-user.target
[Service]
Type=idle
ExecStart=/home/pi/startjupyter.sh
User=pi
WorkingDirectory=/home/pi
[Install]
WantedBy=multi-user.target
Next, set the correct permissions for this file using sudo chmod 644 /lib/systemd/system/raceon-jupyter.service
then tell the systemd to reload its configuration sudo systemctl daemon-reload
and enable raceon-jupyter service to start automatically sudo systemctl enable raceon-jupyter.service
Before rebooting, check that the service starts without any errors using sudo systemctl start raceon-jupyter.service
us and access again the notebook using the browser. In case there are errors, run sudo systemctl status raceon-jupyterlab.service
that will print the console output of the service including the error message.
Ipywidgets is a python package the makes the python code running inside Jupyter interactive by providing user controls such as sliders, buttons, check boxes and other similar interface elements. To install ipywidgets, open in browser the main page of the notebook and select New->Terminal to open a terminal, and type pip install ipywidgets
to install the widgets package using pip. After the installation is completed, create a new Jupyter notebook and paste the code below to test the new package:
from ipywidgets import interact
# Function that will be called for each slider movement
def f(x):
print(x)
# Create a slider and set its default position to 10
interact(f, x=10)
The above code will create a slider below the cell and each slide move call the function f with x equal to the slide position. Later in this tutorial we will use the same concept to change the PID coefficients in real-time while the car is driving on the track.
Currently, the Raspberry Pi Linux kernel has some issues to properly configure the PWM chip to be used from user space and not require root privileges. The work around that we will use consists of two parts, first, configure to automatically change the file permissions after enabling a channel to allow not root users, and second, to automatically export both PWM channels at boot using root. In this way, you do not need to export and unexport the channels, which are the only actions that require root permissions, and you can use Jupyter started as a pi user to enable the two PWM channels, set the period and the duty cycles.
To automatically change the files permissions and allow the pi user use the PWM channels edit the /etc/udev/rules.d/99-com.rules
. Add the text from below at the beginning of the file but before SUBSYSTEM=="gpio*"
using the command sudo nano /etc/udev/rules.d/99-com.rules
SUBSYSTEM=="pwm*", PROGRAM="/bin/sh -c '\
chown -R root:gpio /sys/class/pwm && chmod -R 770 /sys/class/pwm;\
chown -R root:gpio /sys/devices/platform/soc/*.pwm/pwm/pwmchip* && chmod -R 770 /sys/devices/platform/soc/*.pwm/pwm/pwmchip*\
'"
Make sure the added text is written on four lines and both middle lines start with chown
.
Next, we have to create a script that automatically exports the two channels and configure systemd to run it every time at boot. Similar to Jupyter, create a script with nano exportpwm.sh
and paste the following content:
#!/bin/bash
# Export channel 0 and channel 1
echo 0 > /sys/class/pwm/pwmchip0/export
echo 1 > /sys/class/pwm/pwmchip0/export
Save and use chmod +x exportpwm.sh
to make it executable. Next, check that it works with the command sudo ./exportpwm.sh
that should create two additional directories pwm0
and pwm1
inside /sys/class/pwm/pwmchip0/
folder. The output of ls -ahl /sys/class/pwm/pwmchip0/
should look like this:
Now, we should configure systemd to automatically run it at boot. Similar to Jupyter, use nano to create a configuration file sudo nano /lib/systemd/system/raceon-pwm.service
and paste the following content:
[Unit]
Description=Service to automatically export PWM channels
After=multi-user.target
[Service]
Type=idle
ExecStart=/home/pi/exportpwm.sh
User=root
WorkingDirectory=/home/pi
[Install]
WantedBy=multi-user.target
Next, set the correct permissions for this file using sudo chmod 644 /lib/systemd/system/raceon-pwm.service
then tell the systemd to reload its configuration sudo systemctl daemon-reload
and enable raceon-pwm service to start automatically sudo systemctl enable raceon-pwm.service
Before rebooting, we should check that the service starts without any errors. For that, we first have to unexport the two channels such that the script can export them back. To do that, run sudo echo 0 > /sys/class/pwm/pwmchip0/unexport
to unexport the first channel and sudo echo 1 > /sys/class/pwm/pwmchip0/unexport
for the second channel. Now, we can start the service which will reexport these two channels using sudo systemctl start raceon-pwm.service
and result of which we can check again using ls -ahl /sys/class/pwm/pwmchip0/
command. In case there are errors, run sudo systemctl status raceon-pwm.service
that will print the console output of the service including the error message.
Since this setup will automatically export the PWM channel, you do not have to export it manually in python using pwm.export()
and you just have to set the period, duty_cycle and enable which do not require root permission anymore.