Raspberry Pi GPIO Web Dashboard

Introduction: Raspberry Pi GPIO Web Dashboard

When developing Raspberry Pi electronic projects its sometimes necessary to be able to monitor the status of the GPIO pins in "real time" and to easily turn them on and off from a simple interface.

TheFreeElectron's Simple Web Interface (https://www.instructables.com/id/Simple-and-intuitive-web-interface-for-your-Raspbe/) uses PHP and JavaScript to use buttons to control the GPIO pins.

I tried a few different combinations of systems to control before arriving at the final version:

1) Initially I tried using just PHP to display the GPIO table using javaScript to change the pins from high to low as in TheFreeElectron's instructable. However there aren't any commandline functions to read the GPIO pin modes.

2) Then I wrote a c++ script using the wiringPi library to return the pin value and mode, however this could not be used to monitor the status of the pins as the page took too long to refresh which was the only way to have PHP update the table

Finally I arrived at the system this instuctable uses.

PHP is used to generate a HTML page (index.php) containing the basis of the GPIO table.

As in TheFreeElectron's instructable, JavaScript (script.js) is used to call a PHP page (gpio.php) to change the pin values and modes.

A c++ based CGI script (gpiopinstate.cgi) outputs the status of the GPIO pins.

A PHP page (gpio.php) executes the script and converts the output into a HTML page.

This PHP page is requested by JavaScript (script.js) and the output used to set the values and functions of the value and mode buttons. The routine can be set to run repeatedly to give an up-to-date representation of the pin states and is controlled by the check box and button at the bottom.

Its far more complicated than I initially thought but it works so here we go.

Step 1: Requirements (wiringPi, Apache, PHP)

This Instructable will be assuming that the Raspberry Pi is set up with SSH, and samba access.

If not then a little bit of Googling will get you there.

Then follow steps 2and 3 of TheFreeElectron's Instructable to install the wiringPi library and the apache2 web-server with PHP.

I wanted to have easy access to the folders where the web content would be stored so I could develop on my PC as I am running my Pi headless. Therefore I added the following to /etc/samba/smb.conf.

   only guest=no
   create mask=0777
   directory mask=0777
   only guest=no
   create mask=0777
   directory mask=0777

This creates two shares to the folders where the html pages and cgi scripts will live. If these folders don't exist create them and make sure they are owned by your user. When you log into them from another compute use the same user credentials and you shouldn't have a problem.

Of course you can still edit and work on the Pi itself.

You will also have to edit /etc/apache2/apache2.conf so that the Directory directive for /var/www/ looks as below.

<Directory /var/www/>

Options Indexes FollowSymLinks

AllowOverride None

Require all granted

<filesMatch "\.(html|htm|js|css|php)$">

FileETag None

<ifModule mod_headers.c>

Header unset ETag

Header set Cache-Control "max-age=0, no-cache, no-store$

Header set Pragma "no-cache"

Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"




This specifies that the web server should direct any browsers not to cache the page to ensure that up-to-date information is collected by the system. Without this sometimes the table wouldn't update immediately as it is using a cached version of gpio.php.

Next index.php...

Step 2: Index.php

index.php is the main page of this system.

It can be downloaded in the pack at the end of the Instructable.

The key parts of this file are the array at the start that contains the information about the GPIO pins as taken from the "gpio readall" table. This lists the name, BCM, and wPi pin number for each physical pin.

$pinInfo =  array(
                array('3.3v', '', ''), 
                array('5v', '', ''), 
                array('SDA.1', '8', '2'), 
                array('5v', '', ''), 
                array('SCL.1', '9', '3'), 
                array('0v', '', ''), 
                array('GPIO. 7', '7', '4'), 
                array('TxD', '15', '14'), 
                array('0v', '', ''), 
                array('RxD', '16', '15'), 
                array('GPIO. 0', '0', '17'), 
                array('GPIO. 1', '1', '18'), 
                array('GPIO. 2', '2', '27'), 
                array('0v', '', ''), 
                array('GPIO. 3', '3', '22'), 
                array('GPIO. 4', '4', '23'), 
                array('3.3v', '', ''), 
                array('GPIO. 5', '5', '24'), 
                array('MOSI', '12', '10'), 
                array('0v', '', ''), 
                array('MISO', '13', '9'), 
                array('GPIO. 6', '6', '25'), 
                array('SCLK', '14', '11'), 
                array('CE0', '10', '8'), 
                array('0v', '', ''), 
                array('CE1', '11', '7'), 
                array('SDA.0', '30', '0'), 
                array('SCL.0', '31', '1'), 
                array('GPIO.21', '21', '5'), 
                array('0v', '', ''), 
                array('GPIO.22', '22', '6'), 
                array('GPIO.26', '26', '12'), 
                array('GPIO.23', '23', '13'), 
                array('0v', '', ''), 
                array('GPIO.24', '24', '19'), 
                array('GPIO.27', '27', '16'), 
                array('GPIO.25', '25', '26'), 
                array('GPIO.28', '28', '20'), 
                array('0v', '', ''), 
                array('GPIO.29', '29', '21')

This information is used in a for loop that loops through the physical pins and displays the table.

Each cycle of the loop adds one line of the table, and inserts the information for the pins.

The loop gets the information about the pin and then checks to see if the BCM pin number is defined through if ($a_pin_BCM == "").

If it isn't then the pin is a GND, 3v3 or 5v pin and so displays just the pin name. If the number is defined then the loop adds in the cells for the pin numbers and adds in buttons for changing the mode and value.

<td><input type=button onclick='change_pin_mode(<?php echo $a_pin_BCM ?>, 0)' value='' id='mode<?php echo $a_pin_BCM; ?>'></td>
<td><input type=button onclick='change_pin_value(<?php echo $a_pin_BCM ?>, 0)' value='' id='value<?php echo $a_pin_BCM; ?>'></td>
<td><input type=button onclick='change_pin_value(<?php echo $b_pin_BCM ?>, 0)' value='' id='value<?php echo $b_pin_BCM; ?>'></td>
<td><input type=button onclick='change_pin_mode(<?php echo $b_pin_BCM ?>, 0)' value='' id='mode<?php echo $b_pin_BCM; ?>'></td>

Each button is given an id that is either "mode" or "value" plus the BCM pin number. This is used in the JavaScript method get_update() to update the buttons to represent the GPIO pin states.

<table width="80%">
        <td nowrap><button type="button" onclick="get_status()" id="update_button">Update</button></td>
        <td nowrap><input type="checkbox" onclick="toggle_update()" id="update_checkbox">Auto Update</input></td>
        <td width="80%"><div id="workspace" style="text-align: center;"></div></td>

The table at the bottom contains a button to manually update the table, an auto update check box that tells the system to update every 0.5 seconds and a status bar to show information.

This file needs to go in the /var/www/html folder and should replace any index.php or index.html files already present.

Next script.js...

Step 3: Script.js

The JavaScript file contains 4 methods.


This routine is called whenever the auto-update checkbox is clicked. It checks to see if the auto-update checkbox is checked. If so then the routine calls the update method, if not then the routine cancels the running timer and sets the status bar.

change_pin_value( pin, value) / change_pin_mode( pin, mode)

This routine uses a HTTP request to access the gpio.php page supplying GET variables for the pin number and value or mode.

A JavaScript XMLHttpRequest() object is created and a callback function for onreadystatechange is defined.

var request = new XMLHttpRequest();<br>request.onreadystatechange = function() {
        if (request.readyState == 4 && request.status == 200) { ... } // successful

This function is called whenever the state of the request changes.

The readystate goes through the values 1 (OPENED), 2 (HEADERS RECIEVED), 3 (LOADING), and finally 4 (DONE).

The callback function checks the readystate each time it changes and checks to see if the state is 4 (DONE) and that the HTTP status code is 200, signifying that the page was obtained successfully.

If the ready state is 4, but the HTTP code is not 200 then error messages are displayed.

On a successful request the page contents are stored in request.responseText as a string. In this case if the pin value was changed successfully gpio.php returns the string "success". The routine checks the return value, and updates the table by calling get_status() if so.


This method updates the table and buttons to represent the gpio pin status.

As with the above methods a HTTP request is made to gpio.php, however in this case no GET variables are sent, causing the page to return a string representing the status of the GPIO pins as a space delimited string.

On a successful request the response from gpio.php is copied into the data variable.

This is converted into an array with the split function which splits the string at the spaces and stores the results in data_arr.

data_arr = data.split(" ");

The pin values are contained at indices 1 - 28, and the pin modes at indices 30 - 57. The section on the gpio.php page goes into more detail.

for (n = 0; n < 28; n++) {
	// for each pin
	value_button = document.getElementById("value" + n);
	if (value_button != null) {
		// set value button text
		value_button.value = (data_arr[n+1] == 1 ? "HIGH" : "LOW");
		// set class for high/low pin
		value_button.className = data_arr[n+1] == 1 ? "buttonHigh" : "buttonLow";
		// change function of button
		newvalue = 1 - data_arr[n+1]; // new value is opposite to current value
		value_button.onclick = new Function("change_pin_value("+n+", "+newvalue+")");
	mode_button = document.getElementById("mode" + n);
	if (mode_button != null) {
		// set mode button text
		mode_button.value = pinModes[data_arr[n+30]];
		// change function of button
		// not sure about safety of changing pins to ALT modes on the fly so at present just toggles between input and output
		newvalue = data_arr[n+30] > 0 ? 0 : 1; 
		mode_button.onclick = new Function("change_pin_mode("+n+", "+newvalue+")");

The routine loops through the pins from BCM 0 to BCM 27 and changes the pin value and mode depending on the value returned by gpio.php.

For the value buttons the text is changed to LOW or HIGH to represent the state. button.className is also changed to alter the CSS style used for the button. This allows the style of the button in the high or low state to be changed through the CSS in index.php rather than having to code the new style into JavaScript. The function attached to the onclick event is also changed to invert the value of the pin.

For the mode buttons the number returned by gpio.php is converted into a text representation using the array pinModes. These conversions were determined by changing the mode of the pins and checking which alt value was displayed using gpio -g readall. The section on gpio.php has mode information on this. The function attached to the onclick event is also changed such that the mode toggles between "input" and "output". It would be a simple task to have the routine cycle through "input", "output" and all 5 alt modes, however for the purpose of this simple debug dashboard "input" and "output" should suffice.

(I'm also not sure what would happen if a pin was set to an alt mode that it doesn't have a function for, e.g. pin BCM 2 does not have a function on ALT 3, 4 or 5, if anyone had an insight on this I'd be pleased to hear).

Next gpio.php...

Step 4: Gpio.php

This page is never meant to be called directly by the browser, it should only be called by the functions in script.js.

The page first checks that the GET variable "pin" has been supplied.

if (isset($_GET["pin"])) { ... }

If it has then the tags are stripped to get the raw data and the value is checked against some criteria:

  1. is_numeric: checks to see if the supplied variable can be considered a number
  2. >= 0 && <= 27: checks if the value is a valid pin number

(Note: AFAIK the GPIO command doesn't mind if you supply a number outside the range of valid pin numbers, but it's best to be sure).

$pin = strip_tags ($_GET["pin"]);
if ( (is_numeric($pin)) && ($pin <= 27) && ($pin >= 0)) { //test if pin is a number

The page then checks if the GET variable "value" has been supplied and if so the supplied value is stripped and checked that it is a number and that it is either 0 or 1. If the variable passes these criteria then php uses the exec() function to issue the gpio -g write command to change the value of the specified pin, then send "success" to the page. This is read by the JavaScript function as an indication of success.

if ( isset($_GET["value"]) ) {
// use gpio function to change pin value $value = strip_tags($_GET["value"]); if ( is_numeric($value) && ($value >= 0) && ($value <= 1) ) { // ensure value is either 1 or 0 error_log("change pin $pin to value $value"); exec("gpio -g write ".$pin." ".$value); // try to change pin value echo("success"); // return "success", used by JavaScript functions to determine if request was successful }

If the GET variable "value" is not supplied then the page checks to see if "mode" has been supplied, and again strips the tags and checks that this is either 0 or 1, then executes the appropriate gpio mode command to change the pin mode.

As discussed in the script.js section the system is set up to only allow toggling between input and output on the pins, therefore only 0 or 1 is needed. If the alternative modes of each pin are required then they can be set generically by executing the command gpio mode ALT# where # is a number from 0 to 5. Each ALT mode represents something different depending on the pin. e.g. if BCM pin 18 is set to PWM then it shows up as ALT 5.

If the "pin" variable is supplied but not "value" or "mode" then the page displays the value and mode of that pin. This can be used to debug problems but isn't used by the rest of the system.

Finally if the page is called without any variables then the script gpiopinstate.cgi is run using sudo privileges and the output of the script copied into the $pinstate variable.

exec("sudo /usr/lib/cgi-bin/gpiopinstate.cgi", $pinstate);

This variable contains the output of the script as an array where each entry is one line of output from the command. This output is then echoed to the page where the HTTP request in JavaScript picks it up. See the section on gpiopinstate.cgi for more information.

PHP/Apache doesn't normally let "sudo" commands through for obvious reasons, but in this case gpiopinstate.cgi requires it as it is a c++ script which uses the wiringPi library which needs to be run as root.

This can be allowed by running sudo visudo from the terminal and adding the following line at the end of the file.

www-data ALL=NOPASSWD: /usr/lib/cgi-bin/gpiopinstate.cgi

This basically allows the user "www-data" (which runs the apache web server) to execute the script gpiopinstate.cgi using "sudo" without asking for a password.

There is probably some security issue here but it seems pretty secure as the entry only approves sudo for this particular script.

Next gpiopinstate.cgi...

Step 5: Gpiopinstate.cgi

This script is a C++ script that uses the wiringPi library to output the pin status of the specified pin or all the pins.

Firstly the script includes some libraries.

The most important is wiringPi.h, This is installed along with the GPIO utility.

#include <stdio.h>    // required for printf
#include <stdlib.h>   // required for atoi
#include <wiringPi.h> // required for GPIO control

The main() method is the entry point into the script, argc stores the number of arguments provided, and argv[] stores an array of those arguments.

int main (int argc, char* argv[]) { ... }

argc is always 1 or greater as the first element in argv[] is always the commandline that executed the script. Therefore by checking that argc == 2 the script is looking for 1 commandline parameter, in this case a pin number.

if ( argc == 2 ) {
    int pin = atoi(argv[1]);
    //set up wiringPi
    // output information about pin
    printf("Pin state for pin:\n%d\n", pin);
    printf("Mode: \n%d\n", getAlt(pin));
    printf("Value: \n%d\n", digitalRead(pin));  

If a pin number is passed to the script then atoi converts this char* to an int. The wiringPi module is set up in GPIO mode (to match the BCM numbering) and the status of the pin is sent to the output stream. When running from the terminal this would be the terminal but when called from PHP the output is stored in the $pinstate variable.

else {
    // if no argument given then return information about all pins as:
    // (Pin_values) # # # # # .... # (Pin_modes) # # # # .... #
    printf("(Pin_states) ");
    int n;
    for (n = 0; n < 28; n++) {
      printf("%d ", digitalRead(n));
    printf("(Pin_modes) ");
    for (n = 0; n < 28; n++) {
      printf("%d ", getAlt(n));
      // getAlt(pin) returns pin mode as an integer
      // corresponds to the following modes (from experimenting using GPIO)
      // 0 = "IN",1 = "OUT",2 = "ALT5",3 = "ALT4",4 = "ALT0",5 = "ALT1",6 = "ALT2",7 = "ALT3"

If no arguments are supplied to the script then it loops through the pins and prints a string representing their status. For human readability in debugging the format is:

(Pin_states) # # # ... # (Pin_modes) # # # ... #

The first part prints a space separated list of the states of the pins with 0 representing LOW and 1 representing HIGH. The second part prints a list of the pin modes as represented in the comment on the above code. This is the output of the wiringPi command getAlt(pin). I've not been able to find much documentation on this but by setting various modes and comparing the ALT# designation from gpio readall and the mode returned by getAlt(pin), I arrived at the list above.


The C++ needs to be compiled for use. Save the script as "gpiopinstate.c" in /usr/lib/cgi-bin/".

To compile use the following command.

gcc -o /usr/lib/cgi-bin/gpiopinstate.cgi /usr/lib/cgi-bin/gpiopinstate.c -lwiringPi -Wall

This will compile gpiopinstate.c and output the result as gpiopinstate.cgi.

"-lwiringPi" needs to be included to use the wiringPi library. "-Wall" displays all warnings.

If it compiles successfully then congratulations the dashboard is ready to go.

Step 6: The Dashboard

The dashboard can be accessed from the Pi itself by browsing to the page

Alternatively you can use the name of the pi.

You can also find it from another computer on the network, by browsing to the IP address of the Pi or using the name of the machine.

On loading the page the table is displayed then shortly after the buttons update with the current GPIO state. At this point it is useful to click the manual update button a few times and note the time it took to process the request as displayed in the status bar below the table. Running this system on my PiZero and browsing from my laptop it takes about 200ms to process the request, running it from the Pi itself (using Chromium browser) it takes about 300ms. This time will limit the refresh rate of the table. Check the box and the table will refresh 500ms after the previous update was completed.

Now is the time to connect some LEDs to the Pi and use the buttons to turn them on and off, and to connect some push button inputs and see how the pin values are reflected in the table without having to refresh manually.

Included are the code files for all the parts discussed.

I hope this instructable is useful, it is my first!

Step 7: Demonstration

Please watch the YouTube video to see the Web Dashboard in action.

Thanks for watching!

First Time Authors Contest 2016

Participated in the
First Time Authors Contest 2016

Be the First to Share


    • Pocket-Sized Speed Challenge

      Pocket-Sized Speed Challenge
    • Audio Challenge 2020

      Audio Challenge 2020
    • Maps Challenge

      Maps Challenge



    3 years ago

    Proud of you! X