[Solved] Tutorial: GPIO/I2C/SPI-access without root-permissions

WereCatf
WereCatf New Member Posts: 201
edited March 2019 in UP Board Linux
As you may have noticed, by default you do not have access to the GPIO-pins, the SPI-bus or the I2C-bus as a normal user and you have to use 'sudo' to access them or run an application that utilizes them; it is generally a good idea to limit access to such things for security, but on a dev-board like the UP and UP^2 it may be convenient to do development and testing as a regular user. The fix is reasonably simple, as we set the udev-system to change the ownership of the SPI/I2C/GPIO -devices.
Quick setup
To quickly set things up, you can just download the attachment in this post, extract the script and run that script as the user who you want to be able to use GPIO without root -- no need to run the script with sudo or as root, just run it as your regular user. After a reboot, you can just straight ahead down to the section "Using the GPIO as non-root in your code" to get started!
Manual setup
If you do not wish to use the script, you can set things up manually as follows:
SPI-bus access
For SPI-bus access create the file /etc/udev/rules.d/50-spi.rules with the following contents:
SUBSYSTEM=="spidev", GROUP="spiuser", MODE="0660"

Next, copy and paste or type these lines into the terminal as the user you want to give access to the bus:
sudo groupadd spiuser
sudo adduser "$USER" spiuser
I2C-bus access
For I2C-bus access create the file /etc/udev/rules.d/50-i2c.rules with the following contents:
SUBSYSTEM=="i2c-dev", GROUP="i2cuser", MODE="0660"

Similar to above, copy and paste or type these lines into the terminal as the user you want to give access to the bus:
sudo groupadd i2cuser
sudo adduser "$USER" i2cuser
GPIO-access
For GPIO access create the file /etc/udev/rules.d/50-gpio.rules with the following contents:
SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c '\
        chown -R root:gpiouser /sys/class/gpio && chmod -R 770 /sys/class/gpio;\
        chown -R root:gpiouser /sys/devices/virtual/gpio && chmod -R 770 /sys/devices/virtual/gpio;\
        chown -R root:gpiouser /sys$devpath && chmod -R 770 /sys$devpath\
'"

Again, copy and paste or type these lines into the terminal as the user you want to give access to the bus:
sudo groupadd gpiouser
sudo adduser "$USER" gpiouser

Now, there is nothing else to do with the SPI-bus or I2C-bus other than to reboot.

Using the GPIO as non-root in your code
With non-root GPIO-access there is one thing that you need to keep in mind: it takes a little bit of time for udev to set the permissions for all the virtual files the Linux-kernel creates every time you initialize -- or "export", to be more precise -- the pins and in your code you need to take this into account.

Here is a small application in C that simply toggles a pin HIGH and LOW:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <linux/gpio.h>
#include <time.h>
#include <mraa.h>

#define HEADERPIN 11

void sleepMillis(uint32_t millis) {
  struct timespec sleep;
  sleep.tv_sec = millis / 1000;
  sleep.tv_nsec = (millis % 1000) * 1000000L;
  while(clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep, &sleep) && errno == EINTR);
}

int main(int argc, char **argv)
{
	mraa_init();
	mraa_gpio_context gpio;

	if (!(gpio = mraa_gpio_init(HEADERPIN))) {
		fprintf(stderr, "Error exporting pin %d!\n", HEADERPIN);
		mraa_deinit();
		exit(1);
	}

	/* Check if the binary has root-permissions: if not,
	sleep for 100ms to give udev time to set the GPIO-permissions
	correctly for us to use the pin we just initialized above.
	!IMPORTANT! */
	/* Try uncommenting this or changing the amount of time
	we sleep and see what happens. */
	if(geteuid()) sleepMillis(100);

	if(mraa_gpio_dir(gpio, MRAA_GPIO_OUT) != MRAA_SUCCESS){
		fprintf(stderr, "Error setting pin-direction!\n");
		mraa_gpio_close(gpio);
		mraa_deinit();
		exit(1);
	}
	printf("Blink HIGH..\n");
	mraa_gpio_write(gpio, 1);
	sleepMillis(1000); //Sleep one second
	printf("Blink LOW..\n");
	mraa_gpio_write(gpio, 0);
	sleepMillis(1000); //Sleep one second
	mraa_gpio_dir(gpio, MRAA_GPIO_IN);
	mraa_gpio_close(gpio);
	mraa_deinit();
	exit(0);
}

As you may notice, there is a call to geteuid() after mraa_gpio_init() -- mraa_gpio_init() initializes, or exports, the pin we wish to use, then geteuid() call is used to check if the app is running with root-permissions or not. If the application is running with root-permissions, there is no need for a delay as root can access all the file they want, but for non-root permissions a call to sleepMillis() is added to sleep 100ms, so udev hopefully has enough time to set the permissions correctly.

The above convention has to be followed with other languages, too, including shell-scripts:
#!/bin/bash
#Export GPIO3, or pin number 11 on the UP1-board
echo 3 > /sys/class/gpio/export
#Test if we have root-permissions and, if not, sleep for 100ms
if [ $EUID -gt 0 ]; then sleep 0.1; fi
echo out > /sys/class/gpio/gpio3/direction
echo 1 > /sys/class/gpio/gpio3/value
sleep 1
echo 0 > /sys/class/gpio/gpio3/value
sleep 1
#Set the pin back as INPUT and unexport it
echo in > /sys/class/gpio/gpio3/direction
echo 3 > /sys/class/gpio/unexport

And Python:
import RPi.GPIO as GPIO
import time
#We need this
from os import geteuid

# Pin Definitions:
ledPin = 4

# Pin Setup:
GPIO.setmode(GPIO.BCM)
GPIO.setup(ledPin, GPIO.OUT) # LED pin set as output
#If we're not root, sleep 100ms in order for udev to set
#the permissions on the exported GPIO right
if(geteuid() > 0):
	time.sleep(0.1)

print("Here we go! Press CTRL+C to exit")
try:
	while 1:
		GPIO.output(ledPin, GPIO.HIGH)
		time.sleep(0.5)
		GPIO.output(ledPin, GPIO.LOW)
		time.sleep(0.5)
except KeyboardInterrupt: # If CTRL+C is pressed, exit cleanly:
	GPIO.cleanup() # cleanup all GPIO

Comments