Official website for Linux User & Developer
FOLLOW US ON:
Aug
10

Remotely control your Raspberry Pi

by Liam Fraser

Take control of your Raspberry Pi from your smartphone, tablet or PC, from anywhere in the world

People are starting to do all kinds of things with their Raspberry Pi, like having it open and close a garage door, automate lighting and heating in the home, and so on. Even if you don’t yet have a project like this, this tutorial will still serve as a useful introduction to writing web applications with Python.

We’ll be using Arch Linux as the operating system for our project, because it is lightweight and we won’t need a desktop environment.

This tutorial assumes that you have flashed the latest Arch Linux ARM image (in our expert’s case archlinux-hf-2013-05-14) to an SD card. If you haven’t, the instructions for flashing an image can be found on our site. You’ll only need to go up to the step where you write the image to the SD card. You’ll have to adapt the instructions slightly for using the Arch Linux image rather than the Debian one.

Android Raspberry Pi
Control your Garage Door in this tutorial

Resources

A Raspberry Pi

Arch Linux

A second computer – for SSH and testing

Step by Step

Step 01

Logging into Arch Linux

Connect the necessary cables to the Pi and wait for the Arch Linux login prompt. The login is root, and the password is also ‘root’. We’ll change the root password from the default later on.

Step 02

Running a full system update

Arch Linux runs on a rolling release schedule, meaning that there are no version numbers and software is continually updated. The package manager in Arch Linux is called pacman. Use the command pacman -Syu to start a full system update. If for some reason the update fails, try running the same command again. Sadly, the Arch Linux ARM servers tend to be quite busy. There may be a lot of packages to update so it may take a while, especially
because the Pi runs from an SD card.

Step 03

Installing the required packages

Use the command:

pacman -S noip apache python2 sudo

…to install the required packages mentioned at the start of the article. Answer ‘y’ to any prompts you may encounter.

Step 04

Investigating your network

We highly recommend assigning a static IP to your server Raspberry Pi rather than being handed one by your router, because then you’ll always know where to find it on the network, which will be useful for accessing it remotely. You’ll also need a static IP if you want to access the Raspberry Pi from the internet. We’ll need to find out a couple of things about your current network setup before setting a static IP. You can use the commands:

ip addr show dev eth0 | grep inet

…and:

ip route show | grep default

…to do this. We are using grep to filter out any information we don’t need, by only displaying lines containing ‘inet’ to get the IP address of the Pi, and ‘default’ to get the default gateway (the address you route through to get to the internet).

Step 05

Investigating your network

Now that we have found out things about your network, such as your current IP address, the subnet mask and so on, we can set up a static IP address. To do this, we’re going to create a new systemd service. Create the file /etc/systemd/system/static-network.service with the following contents (replacing our IP address and default gateway with ones from your network):

[Unit]
Description=Static IP Connectivity
Wants=network.target
Before=network.target
BindsTo=sys-subsystem-net-devices-eth0.device
After=sys-subsystem-net-devices-eth0.device

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/ip link set dev eth0 up
ExecStart=/sbin/ip addr add 172.17.173.254/24 dev eth0
ExecStart=/sbin/ip route add default via 172.17.173.1

ExecStop=/sbin/ip addr flush dev eth0
ExecStop=/sbin/ip link set dev eth0 down

[Install] WantedBy=multi-user.target

Save the changes, exit nano and then run the following commands to disable DHCP and enable the Ethernet interface and the bridge with a static IP permanently:

systemctl disable netctl-ifplugd@eth0.service
systemctl enable static-network.service

You can now restart the Pi with the command reboot for the changes to take effect.

After restarting, /etc/resolv.conf will have changed, because it was configured by DHCP and we are no longer using that, so you’ll want to run the command:

echo nameserver 172.17.173.1 > /etc/resolv.conf

Once you’ve done this, you’ll be able to resolve DNS addresses like google.co.uk to an IP address.

Step 06

Logging in with SSH

Once the Pi has booted back up, open a terminal on your Linux computer and type ssh root@[ip of your Pi]. Answer yes, to say that you want to connect, and type in the root password, which will still be root. You are now logged in over SSH.

Step 07

Changing the root password

Since we will probably be exposing an SSH login to the internet (we might as well as we’re already exposing a web server), it would be a very good idea to change the password to something much more secure. Type ‘passwd’, and then follow the on-screen instructions to change your password. Your SSH session will stay logged in, but you’ll need to use the new password next time you log in. You may also want to change the contents of /etc/hostname to set the hostname to a self-identifying name, such as remotepi rather than the default: alarmpi. The change won’t take place until after a restart.

Step 08

Configuring Apache

Apache is one of the most popular web servers in the world. It could be argued that a slightly less memory-hungry web server should be used on the Raspberry Pi, but Apache is tried and tested, and there are only ever going to be a few users on a remote-control system such as this. A web hosting company called Mythic Beasts hosted a mirror server with Apache on a Raspberry Pi. It was online for seven months and two weeks before the SD card failed. During that time, it shipped about 1.5TB of traffic.

Common Gateway Interface (CGI) is a standard method for a web server to delegate the generation of web content to executable files. In our case, the executable file will be a Python script.

We need to edit /etc/httpd/conf/httpd.conf to enable the execution of CGI scripts, and to treat any file ending in .cgi as a CGI script. This file is quite long. We’ll be editing a section that begins with about 200 lines into the file. You need to change the Options line from:

Options Indexes FollowSymLinks

to:

Options Indexes FollowSymLinks ExecCGI

Then add the following line under the Options line you just changed:

AddHandler cgi-script .cgi

Finally, you want to go to the section below the one we just edited and change the line:

DirectoryIndex index.html

to:

DirectoryIndex index.html index.cgi

This will automatically run the index.cgi script we’re going to create instead of displaying a list of files that are in the directory.


   DirectoryIndex index.html index. cgi

Step 09

Starting Apache

Use the following command to start Apache at boot:

systemctl enable httpd

Then start it immediately:

systemctl start httpd

If you visit the address http://[your Pi’s IP address], you’ll see a page that lists all of the files in /srv/http. However, as the directory is currently empty, there will be nothing listed there.

Step 10

CGI Hello World

Change directory into /srv/http using the command cd /srv/http and create a new file called index.cgi using the command touch index.cgi. Mark it as executable using chmod +x index.cgi. Once you’ve done that, open it in your favourite editor. The first line tells Apache it needs to use Python to interpret the script. The first line starting with ‘print’ tells Apache to expect HTML content. The print line following that prints a blank line, and then the lines after that print a standard HTML hello world page. Notice how we use the three quotation marks to print multiple lines conveniently.

Refresh the empty index page you saw before and you’ll see ‘Hello World!’.

#!/usr/bin/env python2
# Tell the web server to expect HTML
content
print "Content-Type: text/html"
print

# Print a html hello world
print """
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
"""

Step 11

Creating a menu

So, the first thing we need to do is to create some links that let us choose what to do. We’re going to use a couple of examples: one on running some command on the system and displaying the output; the other an example that could open and close a garage door.

You pass parameters to CGI scripts with the following syntax:

script_name.cgi?var1=value1&var2=value2

Your author wrote some of the dispatching code and a menu that will cause some of that code to execute. Notice how he’s used a function that prints the CGI header and then HTML page using the body and title that you specify, to avoid duplicating code unnecessarily.

At the moment, you will get a 500 error from the web server if you click any links, because nothing happens for any of the commands yet.

#!/usr/bin/env python2

# A CGI dispatcher written in Python by Liam Fraser for a Linux User and
# Developer tutorial.
import cgi

def print_page(title, body):
    # Tell the web server to expect
content
    print "Content-Type: text/html"
    print

    print """
<html>
<head>
<title>{0}</title>
</head>
<body>
<h1>{0}</h1>
{1}
</body>
</html>
""".format(title, body)

def print_menu():
    # Print a menu
    title = "Liam’s Raspberry Pi"
    body = """
<p><a href="index.cgi?action=run_
command">Run a command</a></p>
<p><a href="index.cgi?action=garage_
control">Control the Garage</a></p>
"""
    print_page(title, body)

#
# Start of script
#if __name__ == "__main__":

    # Get any parameters
    params = cgi.FieldStorage()

    # Variable to keep track of if we have a valid input or not
    valid = False
    
    # If we have a key called action in the params
    if params.getvalue("action"):
        action = params.getvalue("action")

        if action == "run_command":
            valid = True
        elif action == "garage_control":
            valid = True
        if valid == False:
            print_menu()

Step 12

A word about debugging

If you get a 500 error when you did not expect to get one, the first thing you probably want to check is that you have printed a header. Other than that, there may be a syntax error in your Python, but Apache isn’t very useful when it comes to pointing that out for you. There are a couple of things you can do. The first is to exit your editor and run the code in the command line to see if Python exits with any syntax errors. You can do this by typing ./index.cgi. You can also look in the Apache error logs for errors. You can keep an eye on the error log with the command tail -f /var/log/httpd/error_ log. You can also print the last 50 (or any other number) lines using the command tail -n 50 /var/log/httpd/error_log.

Step 13

Running a command

The following function displays a page which allows you to submit a command to run and then displays the output. If you look at the HTML form on the bottom, when the Submit button is clicked, the name and value will be sent to index.cgi. For example, if we wanted the output of ps, the request would look like:

index.cgi?action=run_command&cmd=ps

Other than that, the code should be pretty self-explanatory. The only other thing you have to do to get it to work is add the import line:

import subprocess

…and call the function from the dispatcher part of the script, making sure you pass through the parameters:

     if action == "run_command":
         valid = True
         run_command(params)

Give it a try!

def run_command(params):
    # Deal with any run_command
related tasks

    if params.getvalue("cmd"):
        # We have a command to run
        cmd = params.getvalue("cmd")
        # Subprocess.check_output needs an array of parameters
        # split by spaces into a list
        cmd_list = cmd.split()
            try:
                output = subprocess.check_output(cmd_list)
            except:
                output = "Error running command."
            title = "Output of {0}". format(cmd)
            body = "<pre>{0}</pre>".format(output)
            print_page(title, body)
        else:
            # Print the page where wesubmit the command
            title = "Run Command"
            body = """
<form action="index.cgi"
method="post">
Enter a command to run: <input type="text" name="cmd">
<input type="hidden" name="action" value="run_command">
<input type="submit" value="Submit">
</form>
"""
            print_page(title, body)

Step 14

Permissions

Apache runs as the HTTP user; this means that it has very limited privaleges. This is problematic, especially because you’re probably going to need root permissions for any kind of project involving home automation to access the various data interfaces on the Raspberry Pi. To solve this problem, we’re going to use sudo. Change directory to /etc/sudoers.d and create a new file called http. Then open it in your favourite editor.

An obvious thing to test that won’t work is the reboot command. First, you’ll need to find out where the reboot script is located, using whereis reboot. To allow the HTTP user to execute this with sudo, add a line like this:

http ALL=(ALL) NOPASSWD: /usr/sbin/ ip,/sbin/reboot

As shown, you separate multiple commands the user can run with commas. If you now type sudo reboot at the run-a-command screen, the Pi will restart.

Step 15

Garage control example

Your author has written an example that you could use if you were controlling a garage door. The example is pretty straightforward and very similar to the one that runs a command above. As before, you have to call the function near the bottom of the script, by adding the line:

garage_control(params)

…to the block of the garage_control if statement.

def garage_control(params):
    # Define output up here so it’s in the scope of the entire function
output = ""

    if params.getvalue("garage_action"):
        action = params.getvalue("garage_action")

        if action == "Open Garage":
            cmd = "sudo /script/to/open_garage"

        elif action == "Close Garage":
            cmd = "sudo /script/to/close_garage"

        if cmd:
            # Execute the command
            cmd_list = cmd.split()

             try:
                 output = subprocess.check_output(cmd_list)
             except:
                 output = "Error running command."

    title = "Garage Control"
    # Create the body including the
output of a command if we ran
# one
    body = """
<form action="index.cgi"
method="post">
<input type="hidden" name="action"
value="garage_control">
<pre>{0}</pre>
<input type="submit" name=garage_
action value="Open Garage">
<input type="submit" name=garage_
action value="Close Garage">
</form>
""".format(output)

    print_page(title, body)

Step 16

Authentication

If you are planning to expose anything to the internet, you’re going to want some authentication on it. You may also want HTTPS, where the HTTP connection is encrypted, but configuring that is outside the scope of this article.

The first thing we need to do is create an authentication file with the htpasswd tool:

[root@remotepi httpd]# htdigest -c / etc/httpd/auth secure liam
Adding password for liam in realm secure.
New password:
Re-type new password: 

For all subsequent users, remove the -c flag. Now we need to edit the Apache config file again, and add the following lines just after the AddHandler line that we added to the section before:

# Authentication
AuthType Digest
AuthName "secure"
AuthDigestDomain /
AuthDigestProvider file
AuthUserFile /etc/httpd/auth
Require valid-user

Once you’ve done this, restart Apache with systemctl restart httpd and try to visit the site. You’ll be prompted for a username and password and not let in otherwise.

Step 17

Setting up Dynamic DNS

Sign up for No-IP uisng the Free option. Once you have done that, don’t bother downloading No-IP’s client because we’ve already installed it. Go to your email inbox and follow the activation link that was just sent to you by No-IP. You can now sign into your account. Once you have logged in, select the ‘Add a host’ option. Choose a hostname and a domain to be part of from the drop-down list. Leave the Host Type as ‘DNS Host’ and then click the Create Host button. Your author used the hostname liam-ludtest with the domain no-ip.org, so we’d access that using liam-ludtest.no-ip.org.

Step 18

Configuring No-IP

Run the command noip2 -C -Y to be taken through interactive configuration of the No-IP client. We’ve left the update interval to the default of 30 minutes, meaning that the client will check every 30 minutes for an IP address change.

Once you have finished, start the daemon with the command systemctl enable noip2 followed by systemctl start noip2.

After a minute or two, your IP address will be accessible via your No-IP hostname. However, it’s likely that trying it from inside your own house will simply take you to your router’s homepage.

Step 19

NAT port forwarding

It is likely that there are multiple devices behind your router that all use the same external IP address. This is due to the shortage of IPv4 addresses, and because it is more secure to segregate the internet from your internal home network. NAT (network address translation) forwards a port from the router’s external IP address to a computer on the LAN. In this case, we’ll want to forward any traffic for TCP port 22 that comes to your router’s external IP address to the IP address of your Raspberry Pi. TCP port 22 is the port used for SSH. You’ll also want to forward TCP port 80 for HTTP traffic.

The configuration of port forwarding really depends on the router that you are using, so you may have to look it up. The chances are that it will be hidden away in the ‘Advanced’ section of your wireless router. You should be able to access your router by typing your No-IP hostname into your web browser. If not, it should be at the address of your default gateway that we used earlier on.

On your author’s router, he had to go to Advanced>NAT>Port Mapping and add a mapping that looks like the one in the screen above (but with a different IP address) for SSH. He then had to add another for HTTP.

Step 20

Testing it out

That should be it! You’ll have to test the No-IP hostname from outside of your network to verify that it’s working. Obviously, this is a very basic example of writing a web application. Hopefully you can put it to good use!

  • Tell a Friend
  • Follow our Twitter to find out about all the latest Linux news, reviews, previews, interviews, features and a whole more.
    • Pingback: Links 12/8/2013: Netrunner 13.06, New Sabayon | Techrights

    • Pingback: Link: Remotely control your Raspberry Pi » TechNotes

    • Kelly Black

      Fun. Might have been shorter to show how to stub out a Cherrypy server and skip the Apache / CGI bits. Good tutorial none the less. I would recommend it as a good read on knowing how to write a cgi service under Apache in any case!

    • Peder_Aas

      Why is the “DirectoryIndex index.html index. cgi” written with a space in the code-tag? Is it an extra line to be written in the config?

    • Peder_Aas

      There might also be good to change the following in Step 10: “#!/usr/bin/env python2

      # Tell the web server to expect HTML
      content
      print “Content-Type: text/html”

      print”

      to

      “#!/usr/bin/env python2

      # Tell the web server to expect HTML content
      print “Content-Type: text/html”

      print”

      That might be obvious to many people, but it took me some while to figure it out.

    • ami

      Step 5 change [Install] WantedBy=multi-user.target
      to
      [Install]
      WantedBy=multi-user.target

      step 10 change
      #!/usr/bin/env python2
      # Tell the web server to expect HTML
      content
      print “Content-Type: text/html”
      print
      to
      #!/usr/bin/env python2
      # Tell the web server to expect HTML
      print “Content-Type: text/html”
      print

      Step 11 isn;t working

    • ami

      Hey were you able to get step 11 to work?

    • Peder_Aas

      Didn’t try. I only had to get this file to show me a screenshot from another cgi-server. That worked perfectly. I will try step 11 later.

    • Hilman

      Step 11 works with a couple of changes.

      #if __name__ == “__main__”:
      should not be commented out, i.e. it should be
      if __name__ == “__main__”:

      And

      “If valid == False:” should be indented level with “if params.getvalue(“action”):” and not as shown above

    • http://www.intorobotics.com/ Calin Dragos George

      Your tutorial about how to control remote a robot with Raspberry Pi help me and others to get started with Pi. And because I want to help many more hobbyists to start building robots with Pi, I share this tutorial on my post http://www.intorobotics.com/18-excellent-tutorials-compilation-start-working-raspberry-pi/. Thank you!