How to Build a Monitoring Camera with Raspberry Pi and Twilio Programmable Video

March 31, 2023
Written by
Aina Rakotoson
Opinions expressed by Twilio contributors are their own
Reviewed by
Mia Adjei

How to Build a Monitoring Camera with Raspberry Pi and Twilio Programmable Video

This article is for reference only. We're not onboarding new customers to Programmable Video. Existing customers can continue to use the product until December 5, 2024.

We recommend migrating your application to the API provided by our preferred video partner, Zoom. We've prepared this migration guide to assist you in minimizing any service disruption.

You may want to keep an eye on something when far away from it. Nowadays, monitoring cameras can help you do that, but what if you could build your own and make it accessible over the internet whenever you wanted?

In this tutorial, you will learn how to use your Raspberry Pi as a monitoring camera that you can view on a web interface by using Twilio Programmable Video and Python.

What will you build

A Raspberry Pi is a small, single-board computer that can accommodate many kinds of peripherals — devices like a mouse, display screen, keyboard, and more.

In this tutorial, we will focus on its ability to connect to a camera device, as this is what enables you to create a monitoring camera. You will connect a camera module to a Raspberry Pi and stream the video captured by this camera on the internet using Twilio Programmable Video. With this, you can easily access the camera's feed from anywhere.

How it works

The basic principle of Twilio Programmable Video can be summarized in the image below.

Basic concept of Twilio Programmable Video

In order to send/receive video streams over the internet, a user should open a web page with a browser. This web page contacts a back-end server that provides access tokens and also manages video rooms on the Twilio server. With the access token in hand, the web page can now contact the Twilio server and request access to the corresponding room. If the token is valid, the web page can send and receive a video stream from the corresponding room.

Take a look at this article for detailed information about how Video rooms work.

The underlying complexity of this application is hidden behind the web page. All you will need to do in order to send and receive a video stream with Twilio Video is open the web page with your browser and allow the browser to use your camera.

What about the Raspberry Pi?

You'll want to view the video stream from the Raspberry Pi without having to open a browser like you normally would. Here is where Selenium can help you.

Selenium is a tool for automating browsers that can simulate as if a real browser is opening a specific web page, and it supports Python.

So the way the system finally works is:

  1. You open a web page with your browser.
  2. You join a Twilio Video room.
  3. With an action (click on a button) on the same web page, you send a command to the Raspberry Pi to run Selenium, which also completes steps 1 and 2.
  4. As the Raspberry Pi also joins the video room, you can view the video stream from your Raspberry Pi.

The image below illustrates the process.

How the end user and Raspberry Pi share video streams with Twilio Programmable Video

What you need

To build the system, you will need:

Now let’s get started with building.

Connect your camera to the Raspberry Pi

Connect the hardware

First, connect the camera module to your Raspberry Pi. There is a slot where you can plug the camera module into the board. It’s marked with the word “CAMERA”. When you plug in the camera, the board should not be connected to any power source, and you should make sure that the blue side of the connector is facing the Ethernet port. It should look like the image below when connected properly.

Camera device connected to Raspberry Pi board

You can plug your Raspberry Pi into a power source now.

Set up your camera on the Raspberry Pi

Check if your camera is detected by the Raspberry Pi with this command:

vcgencmd get_camera

After running the command, hopefully you see the text below:

supported=1 detected=1, libcamera interfaces=0

If you get detected=1 and supported=1, the hardware part is ready.

If it’s not the case, you may change your camera setting with the following steps.

Open the file /boot/config.txt.

Add the following lines at the end. Also, make sure that all occurrences of camera_auto_detect=1 are commented out.


Then you should reboot your Raspberry Pi to make the change effective.

sudo reboot

Now, if you run vcgencmd get_camera again, you should see that the camera is detected.

The code

The code here is greatly inspired by the official Twilio tutorial Get started with Twilio Video Parts 1 and 2. Start by cloning the code for this tutorial from the following GitHub repo: Twilio Video JavaScript SDK Tutorial for Python. You can do this with the following command:

git clone

Enter the directory of the cloned project and create a new file named .env where you can store your Twilio credentials.

cd intro-video-tutorial-python
touch .env

Inside the .env file, add the following:


You will need to replace the placeholder text (XXXXXXX) with real values for these credentials. You can find your Account SID in the Twilio Console.

Follow this link to learn more about how to do it: Collect account values and store them in a .env file.

You should follow the instructions in the section on creating an API key as well. Once you have generated your API key and secret, you can add these to your .env file too. 

Write the backend service

Let’s start by adding the code that handles the Selenium part on the Raspberry Pi.

In the root folder of the project, create and open a new file with the name

Then add the following lines to it.

import time
from pyvirtualdisplay import Display
from selenium import webdriver
from import By
from threading import Thread

def start_virtual_display():
    """ Start a virtual display to allow using chromium-browser """
    display = Display(visible=0, size=(1600, 1200))

def browse_room(room_name):
    """ Browser room with selenium and allow media stream"""
    options = webdriver.ChromeOptions()
    # allow access to camera device
    driver = webdriver.Chrome('/usr/lib/chromium-browser/chromedriver', options=options)

    text_box = driver.find_element(by=By.NAME, value="room_name")
    submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button")

    print("Submit button")
    return driver

class RpiJoinThread(Thread):

    def __init__(self, room_name, event):
        super(RpiJoinThread, self).__init__()
        self.room_name = room_name
        self.event = event

    def run(self):
        driver = browse_room(self.room_name)
        while True:
            for log in driver.get_log('browser'):
            if self.event.is_set():

The function browse_room() is the main feature in this file. It is where the Raspberry Pi joins a Twilio Video room by browsing the web page and allowing the video stream from its camera.

In order to run this function from your web application, it’s best to run it inside a thread. That’s why we have the RpiJoinThread class.

Note that you also have the function start_virtual_display() which starts a virtual display, as chromium-browser needs a monitor to run properly.

Now import the file in by adding the following line at the start of

from rpi_join import RpiJoinThread
from threading import Event

Then add a new view function in the file that handles the action join_rpi. This will allow you to run the RpiJoinThread with a simple click on the web interface.

@app.route("/join-rpi", methods=["GET", "POST"])
def join_rpi():
    global RPI_THREAD
    if RPI_THREAD:
        RPI_THREAD = None
    RPI_THREAD = RpiJoinThread('myroom', Event())
    return "Room joined"

RPI_THREAD is a global variable used to make sure that we start only one Selenium browser each time.

You will also need to edit the code in the join_room() function a bit, replacing the return statement as follows:

   # return {"token": access_token.to_jwt().decode()}
    return {"token": access_token.to_jwt()}

Write the front-end code

For the front-end part, you need to add a new button that you can click to run the join_rpi() function in the back-end.

In the file templates/index.html, after the form tag, add the following button element.

    <button id="button-join-rpi">Join RPI </button>

Then in the file static/main.js, add an action corresponding to the click on the button “Join RPI”.

const buttonJoinRpi = document.getElementById("button-join-rpi");
const joinRpi = async (event) => {

  const roomName = roomNameInput.value;
  const response = await fetch("/join-rpi", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    body: JSON.stringify({ room_name: roomName }),

buttonJoinRpi.addEventListener('click', joinRpi);

The complete code can be found in this repo

Run the system

Install requirements

To be able to run the app, you need to install some libraries.

First, create and activate a virtual environment inside the directory of the project, using the following command.

python3 -m venv venv
source venv/bin/activate

Next, install the required Python libraries with this command:

pip install twilio pyvirtualdisplay flask selenium python-dotenv

In order to make virtualdisplay work, you may need to install xvfb as well:

sudo apt-get install xvfb

Install chromium-browser

Selenium needs chromium-browser and chromium-chromedriver to browse a web page.

You can install chromium-browser with this command.

sudo apt-get install chromium-browser

Then check the version of your chromium-browser:

chromium-browser --version

The version of your chromium-browser should match the version of chromedriver.

Here is a repository where you can download chromedriver and chromium-browser.

Download the corresponding chromedriver to your chromium-browser and then install it.

At the time of writing this tutorial, the version below was what I installed. Make sure that if you are using a different version of chromium-browser that you install the correct version for when you are following this tutorial.

dpkg -i chromium-chromedriver_104.0.5112.105-rpt2_armhf.deb

Note that the armhf version is what corresponds to the Raspberry Pi version.

Run the app

Now you are ready to run the app. In your terminal with the virtualenv activated, use the following command to run the web app.


Expose your app with ngrok

Ngrok is a free tool that allows you to make your web application accessible from the internet while running it locally.

Download it from the official website, and then install it with this command:

sudo tar xvzf ~/Downloads/ngrok-v3-stable-linux-amd64.tgz -C /usr/local/bin

If you are running ngrok for the first time, you should create an account and get your auth token. Once you get your auth token, run the following command to add it to your configuration, replacing <token> with your auth token:

ngrok config add-authtoken <token>

Expose your web application with ngrok by running this command:

ngrok http 5000

Now, you can access your web app from the internet by using the forwarding URL shown on your ngrok console.

Test the system

Time to check if everything works! Open the ngrok link with your favorite browser on a device that has a webcam. In the input field, enter “myroom”, and click on the button “Join room”.

Your camera video should appear on the web page. Now click on the button “Join RPI” to make the Raspberry Pi join the video room.

After that, the video from your Raspberry Pi camera should also appear on the web page, and voila! You can visualize what happens on the Raspberry Pi side.

Here is an image showing the view from a Raspberry Pi that keeps an eye on my little puppy.

Video tream from raspberry pi and another web client


About me: I’m a lead Python developer with 8 years of experience, passionate about electronics, IoT, and vintage mechanics. You can find me on Twitter or read my blog here: