Skip to the content.

Home > Remote Control of GPIO Circuits via REST/HTML/JavaScript


Remote control of GPIO circuits is achieved by integrating three components:

These components are depicted graphically below and described in more detail in the following sections.


A key feature of the present package is that, once the GPIO circuit is built (bottom left of the figure) and the RpyFlask application is written (top left of the figure), generating the HTML/JavaScript elements and the REST endpoints is almost fully automated.

RpyFlask Application

The RpyFlask application is a central element of the architecture presented above. Internally, the RpyFlask application has the following responsibilities:

These are general responsibilities that apply to all GPIO circuits. As such, they are almost completely hidden from the developer, who only writes the code in the top left of the figure above. This code is reproduced below and found here:

from raspberry_py.gpio import CkPin
from raspberry_py.gpio.motors import Servo, ServoDriverSoftwarePWM
from import app

servo = Servo(
) = 'servo-1'

app.add_component(servo, True)

This code specifies an RpyFlask application containing a servo. It is the basis for HTML/JavaScript and REST API generation, which are explained below.

Apache HTTP Server

This example uses Apache, which can be installed with sudo apt install apache2. An example Apache site configuration file can be found here, though beware of security vulnerabilities like lack of HTTPS and potential exposure of files. Following initial Apache installation, edit the rpy-rest.conf file so that the relevant paths match your local file system. Then proceed as follows to install and enable the site:

   sudo cp /path/to/rpy-rest.conf /etc/apache2/sites-available
   sudo a2ensite rpy-rest.conf

Edit /etc/apache2/ports.conf to include 8080, and set permissions on your home directory with chmod 755 /home/dir so that Apache can read the files. Then restart the server:

sudo systemctl reload apache2

Once the Apache HTTP server is configured, it’s time to generate HTML/JavaScript controls for the RpyFlask application shown above. Consider the following command, which is listed in the top black arrow in the above figure:

cd /path/to/raspberry-py/src/raspberry_py/rest/examples
write_component_files --app servo.servo --rest-host --rest-port 5000 --dir-path servo

The arguments are as follows:

The command generates HTML/JavaScript controls for each of the circuit components in the RpyFlask application. A single circuit component may produce multiple such files, and in the case of our servo example there are two:

The command also generates the supporting files globals.js (global variables like the REST host and port to contact) and utils.js (utility functions) that are used by the JavaScript controls. Consider servo-1-start-stop.html in full detail:

<div class="form-check form-switch">
  <label class="form-check-label" for="servo-1-start-stop">servo-1 start/stop</label>
  <input class="form-check-input" type="checkbox" role="switch" id="servo-1-start-stop"/>
<script type="module">
import {rest_host, rest_port} from "./globals.js";
const servo_1_start_stop = $("#servo-1-start-stop");
servo_1_start_stop.on("change", function () {
    url:":checked") ? "http://" + rest_host + ":" + rest_port + "/call/servo-1/start" : "http://" + rest_host + ":" + rest_port + "/call/servo-1/stop",
    type: "GET"

The general pattern for the HTML/JavaScript files is to specify an HTML control followed by JavaScript that connects the control with the circuit component running in the RpyFlask application. Here we have a labeled toggle switch, and the JavaScript calls either the servo-1/start or servo-1/stop REST endpoints depending on the status of the switch. These HTML/JavaScript files can then be embedded within a full HTML page such as this, which is rendered in a browser as shown below:


Flask REST Server

As with the Apache HTTP server, any modern HTTP server should suffice for serving the REST endpoints that are contacted by the JavaScript described above. For simplicity, we use Flask’s built-in server, which is started as follows:

flask --app servo.servo run --host

The arguments are as follows:

The output of starting the Flask server should resemble the following:

 * Serving Flask app 'servo.servo'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (
 * Running on
 * Running on
Press CTRL+C to quit

You should now be able to visit from any web browser that has access to the host IP address. The page should appear as shown above, and interacting with the controls should cause the Flask server to emit messages such as the following (e.g., for the servo’s power switch and slider): - - [02/Dec/2022 22:51:26] "GET /call/servo-1/start HTTP/1.1" 200 -
INFO:werkzeug: - - [02/Dec/2022 22:51:26] "GET /call/servo-1/start HTTP/1.1" 200 - - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:1 HTTP/1.1" 200 -
INFO:werkzeug: - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:1 HTTP/1.1" 200 - - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:2 HTTP/1.1" 200 -
INFO:werkzeug: - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:2 HTTP/1.1" 200 - - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:3 HTTP/1.1" 200 -
INFO:werkzeug: - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:3 HTTP/1.1" 200 - - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:4 HTTP/1.1" 200 -
INFO:werkzeug: - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:4 HTTP/1.1" 200 - - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:5 HTTP/1.1" 200 -
INFO:werkzeug: - - [02/Dec/2022 22:51:27] "GET /call/servo-1/set_degrees?degrees=int:5 HTTP/1.1" 200 -

If the circuit is properly built and powered on, the servo will activate and move accordingly. MacOS runs AirPlay Receiver on port 5000. If you get unexpected errors when making REST calls to Flask, try disabling AirPlay Receiver at System Preferences => Sharing => AirPlay Receiver.

Example: Freenove 4WD Smart Car

The Freenove 4WD Smart Car comes with Python software, including a remote control interface. I have reimplemented most of the capabilities using the present Python package. The following is a screenshot of the control screen for the car based on the above framework (read more here):