15 Feb 2014

Raspberry Pi control from mobile device or desktop web browser

In my disco ball hack project, I have been using my mobile phone browser to send commands to my Raspberry Pi. In this post, I will give you a quick overview on how to do the same thing and we will take a look at the code involved. I hooked up a LED and a button to my RPi so we can read the state of the button and toggle the LED. I'm using a self flashing RGB LED that I bought on Ebay, it looks cool :)

There are multiple ways to connect to the Raspberry Pi from another device. It could have been a usb-serial link, bluetooth or even raw socket programming. Also, you could have used other methods  to create the user interface by using native programming on the device :  Android or iPhone app, Visual Studio on the PC... The biggest advantage of using HTTP with a web server on the Pi is that it will work with all devices that have a browser. Also, you only need to know a bit of HTML and JavaScript, you don't need to learn all the other SDK and languages. I think that it's much easier to design a user interface using HTML and adding a bit of CSS for improving the look of the page than learning all the GUI toolkits out there. Using HTML5 and JavaScript is trending right now with all the web development. In Windows 8, apps can be created natively in HTML5 and JavaScript. Some of the new operating systems like Firefox OS and Tizen use HTML for everything. Anyway, let's get started with our application.

For the server side software part, I'm using Python and the web framework Flask. There is a ton of these Python web frameworks (Django is the most well known) but Flask is great because it is really simple and does not need any configuration. For the HTML part, I'm using a bit of JQuery Mobile because it helps making a great looking page in a few line of HTML. There is also another reason to use JQuery: Ajax. (Asynchronous JavaScript and XML)

In a typical webpage without Ajax in which you want to send data to the server, you have to make an HTML form with a submit button. Once you press submit, a POST or GET request is done, your data is transmitted to the server and the page is completely reloaded with new data or you are redirected. With Ajax, you can easily send data to the web server without disrupting the page displayed. It is also possible to request JSON data from the server and modify the DOM (update only certain element of the page).

The simple circuit used

My circuit is an LED connected on P1 (WiringPi pin numbers) in series with a 470 ohm resistor. I also have a button between P0 and GND. I don't need a pull-up on the button because I'm using the internal pull-up of the Raspberry Pi GPIO. The circuit is even simpler like that.

Python code to access the button and LED

This code is fairly simple, I used the standard Raspberry Pi Python module to read/write to the GPIO. I made small utility functions so that later it is easier to read the code in the server Python file. The file name is Pins.py and I will use this as a module in the web server code.

import RPi.GPIO as GPIO

# BCM to WiringPi pin numbers
P0 = 17 # LED pin
P1 = 18 # Button pin

def Init():
    GPIO.setwarnings(False) # suppress GPIO used message
    GPIO.setmode(GPIO.BCM) # use BCM pin numbers
    GPIO.setup(P0, GPIO.OUT) # set LED pin as output
    # set button pin as input
    # also use internal pull-up so we don't need external resistor
    GPIO.setup(P1, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def LEDon():
    GPIO.output(P0, GPIO.HIGH)

def LEDoff():
    GPIO.output(P0, GPIO.LOW)

def SetLED(state):
    if state:

# the "not" is used to reverse the state of the input
# when pull-up is used, 1 is returned when the button is not pressed
def ReadButton():
    if not GPIO.input(P1):
        return True
        return False

# if not used as a module (standalone), run this test program 
if __name__ == "__main__":
        #clean exit on CTRL-C

Web server code

With Flask you can create a working web server in half a page of code. The Index function send the page to the web browser. Notice that the page is rendered only once, the rest is handled with Ajax. The function names that starts with the underscore are the Ajax request. They are called with the GetJSON function with JQuery (see the web page JavaScript in the next section). The _led function does not return JSON, but extract the state parameter from the GET url from the Ajax request to set the state of the LED. The _button function does not have parameter but instead return a JSONified text string corresponding to the button state. As a bonus, when the page is rendered first, I send the uptime of the Raspberry Pi. You can try to type "uptime" in a Unix terminal, you will see a status string displayed. It gives you the time, uptime, # of connected users and load average. I only wanted the uptime so I parsed it.

By the way, to get the Flask module on your Raspbian Python folder, enter the following commands:
sudo apt-get install python-pip
sudo pip install flask

The first command is to install pip (a tool for installing and managing Python packages).

from flask import Flask, render_template, request, jsonify
import Pins

app = Flask(__name__)

# return index page when IP address of RPi is typed in the browser
def Index():
    return render_template("index.html", uptime=GetUptime())

# ajax GET call this function to set led state
# depeding on the GET parameter sent
def _led():
    state = request.args.get('state')
    if state=="on":
    return ""

# ajax GET call this function periodically to read button state
# the state is sent back as json data
def _button():
    if Pins.ReadButton():
        state = "pressed"
        state = "not pressed"
    return jsonify(buttonState=state)

def GetUptime():
    # get uptime from the linux terminal command
    from subprocess import check_output
    output = check_output(["uptime"])
    # return only uptime info
    uptime = output[output.find("up"):output.find("user")-5]
    return uptime
# run the webserver on standard port 80, requires sudo
if __name__ == "__main__":
    app.run(host='', port=80, debug=True)

Web page HTML code

In the web page, I import everything needed to get JQuery Mobile working. In the script section, I have two JavaScript functions : the first one detect a change on the slider-switch and send the state of this switch with an Ajax GET to the webserver, the second one is called once each 500 ms to get the button state. The rest of the HTML is the minimal structure of the page.

<!doctype html>
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.css" />
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.js"></script>

h3, h4 {text-align: center;}
span {font-weight: bold;}

<script type=text/javascript>
    // When the LED button is pressed (change)
    // do an ajax request to server to change LED state
        $.getJSON('/_led', {state: $('#flip-1').val()});

    // periodically (500ms) do an ajax request to get the button state
    // modify the span tag to reflect the state (pressed or not)
    // the state text comes from the JSON string returned by the server
    function button() 
        $.getJSON('/_button', function(data)

<!-- Simple JQuery Mobile page that display the button state on the breadoard -->
<!-- You can also change the LED state with the slider switch -->
<!-- The Raspberry Pi uptime is displayed in the footer (Jinja2 expands the template tag) -->

<div data-role="page" data-theme="b">
  <div data-role="header">
    <div><h3>Raspberry Pi Web Control</h3></div>

  <div data-role="content">
    <p>The button is <span id="buttonState"></span></p>
    <select name="flip-1" id="flip-1" data-role="slider" style="float: left;">
        <option value="off">LED off</option>
        <option value="on">LED on</option>
 <div data-role="footer">
    <div><h4>This Raspberry Pi has been {{uptime}}</h4></div>

In action : demo video on Youtube

Here is a demo of the server running and being controlled by my mobile phone. On my main computer, I connect to the Raspberry Pi with SSH. I then start the python application. If you look closely on the computer screen around 0:15, you will see a lot of requests being made to the server. This is due to the 500ms timer sending the Ajax request to read the status of the button.

I really hope that this code can help you in your projects. When I started doing electronics stuff in 2007, I was dreaming about doing something like this. At that time, we did not have smart phones and low cost Linux single board computer. Things changed a lot since!


  1. Great tutorial, thank you ;)
    If you have an idea how to insert several of these buttons ? Generate from the array or manually copy/paste your code ?
    This is my first meeting with flask.

    1. I would generate the buttons from an array if the usage of the buttons are similar (e.g. having 16 buttons for LEDs). You can do this with Javascript, or use the Jinja2 templating language provided by Flask. If the buttons are unrelated, you can just copy paste the button part of the code in the HTML and Python file.

  2. Hello,

    I've used your work to create a Garage remote control.
    However I'm getting an issue when sending commands.
    I've created a Github for it, if you don't mind having a look, it would be greatly appreciated!
    I've posted it at https://github.com/amayii0/GarageOpenerR1


    1. I've made a pull request to your repo

    2. Thanks that works perfectly now. I've also updated the wiki page @github to provide some more info.

  3. Frederick,

    I am trying to implement your example, but keep running into a problem... Template not found.

    My template, like yours, is index.html and is located in the same directory as my server script. I have tried it simply as render_template("index.html"... and with the full path with the same results.

    Do you have any idea what I am doing wrong?



    1. try to put index.html in a directory called templates
      take a look here for an example project https://github.com/amayii0/GarageOpenerR1
      Good luck :)

    2. Perfect!!! That worked.


  4. How would the code of the server look like if i don't want to read button state? I don't recognize the part of the code responsible of (i know, my english is bad) reading or writting state ;_;

  5. The part of the code is in the _button() and _led() function. If you don't want to read the button state, just remove the _button() function

  6. Hi Frederick, thank you for your wonderfull work. I tried a number of examples for my project but this is the first that actually did what i wanted :). I got 2 questions if i can.
    1) when i press on on my desktop and go to visit site on other device like smartphone the button state doesn't seem to sync with other devices. Is there a way to do that?
    2) Not that important but can i change the button slider with like an image that changes during on/off status?

    Again thank you for your work and your help !

    1. Hi Bart, thanks for your compliment.
      #1 Yes there is a way to do that. I'm doing a project with a friend and that is what we did. This is a home automation project where we controls a pool pump and other stuff in the backyard. The project is on GitHub. (https://github.com/SupremeSports/Domotique)
      The way it is implemented is using a library called SocketIO. this library enable server side event to send to each client that changes has been made to a switch state without having to refresh. However, it may be a bit complicated for you depending on how good you are with Python and Javascript.

      The other way is to poll the button state. We did that in an earlier commit. Git is really great because you can see how we did that before we changed how it works.

      See here : https://github.com/SupremeSports/Domotique/blob/892d58e69ceacdfbb33dfae2198595eab7a2558a/templates/index.html#L82
      On line 88 you have the function to write the data to the server.
      On line 92 you have the function that refresh the data every 500ms
      Use that as a starting point.

      #2 Yes you can.
      just replace the switch by an image tag and add a click action to it. From there, you can change the image source to the different state.

      Good luck :)

    2. Thx, Frédérick. I will experiment with it :)

  7. Hi. First of all, it works excellent, thanks a lot.
    Second, one question, Do we really need the internet? or If I use a router without Internet connection, what happened? thanks in advance.

    1. No need for the internet. As long as it is in a LAN. You could also make a wireless hotspot with a dongle and connect to the Pi. You could also set a static IP address and connect to it with an ethernet cable straight from a laptop.

  8. Out of curiosity, wouldn't the setTimeout function create recursion in your code which would eventually lead to memory problems?

    1. Good question. In fact, button is not called, I just create a reference to a function that calls button. This means that it is not recursive, just scheduled to be called after a timeout.

  9. Hi Fred I am a starter to this coding world can you explain me how this GetUpTime works
    Thanks in advance

    1. With the check_output function, I get the output of the uptime Linux command. The output looks like this : 08:11:22 up 74 days, 34 min, 2 users, load average: 0.40, 0.36, 0.36
      . The part that interest me is the uptime only, not the load or user stuff so I find the position of the "up" and the "user" and get whats in between. output.find("up"):output.find("user")

      When I have the uptime string, I send it to my template in the render_template function. Do you understand?

  10. "GET /_led?state=off HTTP/1.1" 500 -
    What ?!

    1. This happen when there is an error in the Python part of the program. The code 500 means internal servor error. Identation issue maybe? Did you add some code?

  11. hello.
    Thank you for code. All works great.
    I modified you code to control the led via button on a page and stucked with one thing.
    After i set led to 'on' state and then refresh page select resets to "off" state while led stays in "on".
    If it is possible to read the state of pin after reload of page and set the select to proper state?
    Thanks for help.

  12. Yes, there is a lot of ways to do it. You can just refresh the page continually or read the state of the LED before displaying the page. One of my friend did it on one of his project based on my code, see this commit here : https://github.com/SupremeSports/Domotique/commit/d51595138cadc77a174695abc31b2881388e2c56

    1. Sorry, but i failed to understand how to implement this.
      Could you please show it?

    2. What do you mean by "could you please show it"? You want me to do the code?

      What you want is exactly at this line:

  13. Hi!
    Thank you for tutorial. All works great.
    I added code line output (def _change ()), it works , but the browser then goes to the address, how to make I do not know. GOOGLE did not help me . Help me please.
    Code on GitHub - https://github.com/VitPN/FlaskRPi

    1. I don't really understand your question the way you formulated it.
      Something I see in your code is that you force the function to only execute on POST request, so it will never get to the "else" part. You should try putting : methods=['GET', 'POST'] to allow GET request via browser.

    2. This comment has been removed by the author.

  14. Woohoo!!! At last! I've lost count of the number of tutorials I've read that explain how to control an LED from a browser when really all I want is to read the input from a switch to a browser. Thank you so much for sharing. I haven't tried your solution yet (Brain needs a rest), but one question occurs to me...

    I have 390 switches connected to my RPi... Looking at your code, you "periodically (500ms) do an ajax request to get the button state". In my project this would mean 780 ajax requests per second, which seems very inefficient. Do you know if there's a better way to do this, such as using interrupts for example?

    Thanks in advance for any help,


  15. I saw this in one of your previous comments: "The way it is implemented is using a library called SocketIO. this library enable server side event to send to each client that changes has been made to a switch state without having to refresh. However, it may be a bit complicated for you depending on how good you are with Python and Javascript."

    It sounds like this would be the answer to my question. I'm a confident programmer (PHP, JS etc) but new to Node.js and SocketIO. The socket.io website has some tutorials which I'll follow: http://socket.io/get-started/chat/ for example.

    Is there anything else you can recommend to help me get my 390 switches working without having to make so many Ajax requests?

    Thanks again Frédérick, I really appreciate your help.

    1. 390 switches is only 390 bits. Aproximately 50 bytes of data. Thats not a lot of data. You could get that in a single request and dispatch it to your DOM using a javascript function.

      If you want that to be realtime, socketio is the way to go. See this : https://github.com/miguelgrinberg/Flask-SocketIO

      I would be interested to know what those 390 switches are for. Looks like a lot of wiring :P

  16. Thanks for your response. I'm in the process of making an electronic board-game. I should really blog about it, when I do I'll share a link.

    I just followed your tutorial and everything is working. For others following this, the folder structure should look something like this:


  17. I got your excellent example working with the Safari browser on MAC Book Pro, iPhone, iPad and Chrome on Android. Thank you very much for all of your effort. It does not however work on a PC with Internet Explorer. I can see that setTimer does not continue to run. I can toggle the LED twice and then things stop working. The debug report on the rpi side stops updating when the connected browser is IE 9-11 or MS Edge on Windows 10. For Safari or Chrome it keeps updating every 500 ms. I did a little Google searching and found something about the call to setTimer is not compatible with IE. Is there a fix for this?

    1. took me 1 hour to figure that out... The problem is that the Edge browser try to be too intelligent and it sees that the request is made at the same address. It tries to cache the response. You can see that in the developer tools. Add this line of code in your document ready event : $.ajaxSetup({ cache: false });

      This will add some incrementing numbers at the end of the request, so Edge won't be able to cache.

    2. That works! Here is what I added to the /templates/index.html file:


      // This function prevents MS IE and Edge from caching responses.

      Thank you again for your effort and help. I will be using this code in an amateur radio equipment monitor that I am building with rpi. I wanted to be able to use a browser on any device in the house to monitor the equipment and this fills the bill. Very simple and cool solution.


  18. This is great, thanks! Note that for Python 3 you'll receive this error: "TypeError: Type str doesn't support the buffer API"

    The solution is to make the following change in GetUptime():
    OLD: output = check_output(["uptime"])
    NEW: output = check_output(["uptime"]).decode('utf-8')


  19. Hello thank you very much for this work I have a question, when I try to detect the button I need to update the web page every time ? can you help me


    1. Hello Lyes. Let me go a little bit more in detail about this.

      When you request a web page from a server, you get a static HTML page. You can make that when you request that page, the server put in the page template the status of the button.

      The problem with that is that you have to manually refresh the page to see the button state. You could put in JavaScript an auto page reload but that would be bad user experience. The web technologies in the current state let us do more than that.

      You then have 2 options:
      1. polling the server with an Ajax request
      An ajax request is an asynchronous communication (data transfer that happen in your browser without the user intervention)
      Javascript let you update the DOM on the fly, so you can set up a timer (look for the setInterval() function) that will do a request to your server to know the button state and modify the html element that contain the information about your button state to reflect that state. This is not really efficient because you have to do the request often. For a small application, this is okay but that would be really inefficient for large scale web applications. (like Facebook or Twitter).

      2. Using websockets / server push
      Using websockets let you establish a permanent 2 way communication between the server and client (web browser). The server can update the client with new info (button state) only when there button state change. This can reduce bandwith a lot. To do this, there are really good libraries out there that you can take a look at ( http://socket.io/ )

      I hope this is going to help you a little bit in your research. Unfortunately I don't have enough time to put up a new blog post with some example code. If you search a little bit on Google though you should find what you are looking for :)

  20. Thanks a bunch for this detailed tutorial.

  21. sir how can i control led from any where in internet that means not in local network?

    1. You need to forward the port you are using for the web server on your router and use your external ip assigned by your isp

  22. I was looking exactly for this.. thx..
    btw is it possible to toggle the switch in the webpage along with the physical switch on the board.. ie if the if the physical switch on the board is pressed & the led turns on, then the toggle switch should also toggle to the ON state.. similarly when the LED is switch off by pressing the physical switch on the board, the toggle switch in the browser should go to the off state

    1. Yes, this is possible. Tell me if you need help implementing this

    2. My new Pi is on its way... once i receive it i may need help bcoz i don't know how to code for DOM update according to change in state of LED

    3. BTW is it possible to do the same using Arduino uno & wifi module.. ie the same functionality i mentioned above??

    4. Yes it would be possible to achieve the same end result, but I think that anything web server related is easier to do on a Raspberry Pi (or any Linux board). You won't have python on an Arduino, code will be much more low level. Raspberry Pi are not much pricier than the Arduino + Wifi module.

  23. Hi. First of all, it works very well, thanks a lot.
    Second, one question, can i use this program to control four led ?
    can you help me to develop this solution?
    thanks a lot

    1. Yes you can control four LED, I can give you some help, it should be easy to change the code to do it. Can you give it a try first?

  24. Hi,
    I am trying to implement your code, but I am getting an error when calling the page from a browser: TemplateNotFound: index.html
    Everything in in my home folder, so I changed the reference to:
    return render_template("/home/pi/index.html", uptime=GetUptime())
    But still get: TemplateNotFound: /home/pi/index.html

    Here are my file permissions:
    -rw-r--r-- 1 pi pi 1371 Jul 28 10:19 do_flask.py
    -rw-r--r-- 1 pi pi 1959 Jul 28 09:37 index.html
    -rw-r--r-- 1 pi pi 1091 Jul 28 09:44 Pins.py
    -rw-r--r-- 1 pi pi 1283 Jul 28 10:14 Pins.pyc

    Here are the python errors:
    pi@raspberrypi:~ $ sudo python do_flask.py
    * Running on
    * Restarting with reloader - - [28/Jul/2017 10:20:21] "GET / HTTP/1.1" 500 -
    Traceback (most recent call last):
    File "/usr/share/pyshared/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
    File "/usr/share/pyshared/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
    File "/usr/share/pyshared/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
    File "/usr/share/pyshared/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
    File "/usr/share/pyshared/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
    File "/usr/share/pyshared/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
    File "/usr/share/pyshared/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
    File "/usr/share/pyshared/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
    File "/home/pi/do_flask.py", line 9, in Index
    return render_template("/home/pi/index.html", uptime=GetUptime())
    File "/usr/share/pyshared/flask/templating.py", line 127, in render_template
    return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
    File "/usr/lib/python2.7/dist-packages/jinja2/environment.py", line 830, in get_or_select_template
    return self.get_template(template_name_or_list, parent, globals)
    File "/usr/lib/python2.7/dist-packages/jinja2/environment.py", line 791, in get_template
    return self._load_template(name, self.make_globals(globals))
    File "/usr/lib/python2.7/dist-packages/jinja2/environment.py", line 765, in _load_template
    template = self.loader.load(self, name, globals)
    File "/usr/lib/python2.7/dist-packages/jinja2/loaders.py", line 113, in load
    source, filename, uptodate = self.get_source(environment, name)
    File "/usr/share/pyshared/flask/templating.py", line 64, in get_source
    raise TemplateNotFound(template)
    TemplateNotFound: /home/pi/index.html - - [28/Jul/2017 10:20:21] "GET /?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 - - - [28/Jul/2017 10:20:21] "GET /?__debugger__=yes&cmd=resource&f=jquery.js HTTP/1.1" 200 - - - [28/Jul/2017 10:20:21] "GET /?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 - - - [28/Jul/2017 10:20:21] "GET /?__debugger__=yes&cmd=resource&f=source.png HTTP/1.1" 200 - - - [28/Jul/2017 10:20:21] "GET /?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -

    I do not know where to go next on this - any ideas?

    1. the default template folder is "templates" and need to be located in the root directory of your application. You can change the default folder when you create the application object.

      Take a look at : http://flask.pocoo.org/docs/0.12/api/
      and search for "template_folder"
      you will see that default value

      Tell me if you get it to work :)

  25. Frédérick,
    Thanks so much for your original post and your very speedy response to my query. I made the templates sub-folder and moved my index.html file to there and it all works fine, now.
    I am making a project which uses an ultrasonic sensor and wanted to report back to a web page and so had installed Apache, but this is so much simpler. I found that I had to stop the Apache server - but I guess they cannot both be serving.
    Thanks again.

    1. My pleasure. I really enjoy helping other people in their projects and giving some advice. I would like to spend more time putting new content on the blog but I do a lot of extra hours at work (I spent 17 weeks away from home last year).

      Concerning Apache and flask, they should be able to work together if you use different TCP/IP ports.

      try something like
      app.run(host='', port=8000, debug=True)
      and access your webpage with

  26. Can you please explain the folder structure in detail. i have created all the files.ie


    & then in a subfolder 'templates' & put the html file in it

    But where should i place this whole project folder?

  27. Something like /home/pi/script1

  28. I found this article easy to understand and very helpful. Can’t wait to see the other posts. Thank you for sharing!

    1. I'm glad it has been helpful to you :)