A Simple Raspberry Pi CCTV With Online Video Feed and Control Interface

by Kuumaver in Circuits > Raspberry Pi

142 Views, 2 Favorites, 0 Comments

A Simple Raspberry Pi CCTV With Online Video Feed and Control Interface

Screenshot 2025-07-28 200214.png
Screenshot 2025-08-01 140008.png

Here's a simple surveillance camera using a Raspberry Pi Zero 2w and a Flask Webserver. This guide aims to help everyone who wants to get started with Raspberry Pi through a very easy to do project. I wanted to write this for documentation, but I am open to share it with everyone who reads this post, along with some references I used.


Supplies

Screenshot 2025-07-28 200053.png
Screenshot 2025-07-29 121122.png
Screenshot 2025-07-28 200133.png
Screenshot 2025-07-28 201933.png
Screenshot 2025-07-28 215927.png

To work on this project, these are the things you would need to have:


  1. Raspberry Pi - I chose the Zero 2w for this project because of it's small size. Regarding it's processing performance though, I would upgrade for a bigger board (like the RPI 4 or 5 maybe) if I wanted to add more functions to it like facial recognition. Here's a good YouTube video about this which I also used for inspiration for the project if you're interested.


  1. Camera Module - The one I am using is not an official Raspberry Pi Camera Module. I found this a lot cheaper, compared to the official one. It may be lacking some functions the original one can do but it's enough to do the job I want it to do properly. Even with that, I do recommend getting the official one if you can because of the more things you could definitely do with it, allowing you to explore more. If you are interested, you should check their documentation here.


  1. 9g Servo - The servo will be used for the rotation function of the camera to view blind spots. This servo is small but it's enough to provide rotations for the camera.


  1. Micro SD card - This will be used to flash the Raspbian OS image into the Pi. I am using a 32GB card for this one (I would recommend this too, or you step up a little bit more to 64GB or even 128GB).


  1. Charging cable and Adapter - Use a suitable one for the Pi (I will be using a micro USB for Zero 2w). This will be used to power our project.

Setting Up the Your Raspberry Pi

Screenshot 2025-07-28 220225.png
Screenshot 2025-07-28 221630.png

You would first need to install the Raspberry Pi OS imager from Raspberry Pi's official website.

For a more detailed instruction on how to set your Pi up, I recommend checking this video out.

Just to further note, I went with the recommended option, which was Raspberry Pi OS 64-bit. I chose the one with desktop available since it has some of the libraries we would need already, like the picamera2 library.

Setting Up Hardware

Screenshot 2025-07-29 120232.png
Screenshot 2025-07-29 132116.png
Screenshot 2025-07-29 121331.png
Screenshot 2025-07-29 212436.png

Once you got your Pi good to go, it's time to connect things up.


Connecting the camera

Start by connecting your camera into the Pi, if you are using the same board as I am (Raspberry Pi Zero 2w), you should see a small tab on right side near the power (PWR) port (as shown in Figure no. 1 of this step). Now, if you look at your camera module, there should be the shiny side at the end of the connector (as shown in Figure no. 2 of this step), you connect it to the tab on the Pi with the shiny side facing on the board (as shown in Figure no. 3 of this step).


Connecting the Servo

For the camera to rotate, it would need to have a servo. Connect the Servo's red wire into the 5v pin, the brown wire into the ground(GND) pin, and the orange wire into GPIO pin 27 in the Pi (as shown in Figure no. 4 of this step). You may refer to this for the GPIO pin layout on your Raspberry Pi.

Code

Python Script

from flask import Flask, render_template, Response
from picamera2 import Picamera2
from threading import Thread
import time
import io
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

servo = 27
GPIO.setup(servo, GPIO.OUT)
pwm = GPIO.PWM(servo, 50)
pwm.start(0)


app = Flask(__name__)
camera = Picamera2()
config = camera.create_video_configuration(
main={"size": (1024, 768)},
controls={"FrameDurationLimits": (15000, 15000)},
buffer_count=2
)

camera.configure(config)
camera.start()
time.sleep(1)

#start of camera codes==================================================================

global startTime
startTime = time.time()
global streamFps
streamFps = 33

def millis():
return round((time.time()-startTime)*1000)

def cameraStream():
global frame
global streamFps
lastFrameTime = 0
while True:
if ((millis()-lastFrameTime)>=streamFps):
print("Last frame time " + str(millis()-lastFrameTime))
stream = io.BytesIO()
camera.capture_file(stream, format='jpeg')
stream.seek(0)
frame = stream.read()
lastFrameTime = millis()
stream.truncate()
def videoStream():
global frame
lastFrame = None
while True:
if frame != lastFrame:
lastFrame = frame
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

#end of camera related code ========================================================

@app.route('/')
def index():
return render_template('Cyver_index.html')

@app.route('/video_feed')
def video_feed():
return Response(videoStream(), mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/right')
def right():
pwm.ChangeDutyCycle(2)
time.sleep(0.3)
pwm.ChangeDutyCycle(0)
return render_template('Cyver_index.html')

@app.route('/left')
def left():
pwm.ChangeDutyCycle(12)
time.sleep(0.3)
pwm.ChangeDutyCycle(0)
return render_template('Cyver_index.html')


if __name__ == '__main__':
streamThread = Thread(target=cameraStream)
streamThread.start()
app.run(host='0.0.0.0', debug=False)


Here is the link of where I referenced the code for the camera from, if you're interested.


Paste this code into Thonny, then save the file with using this format: name.py


To run the code, you can press the run button in Thonny (the button on top of the editor pane that looks like a green play button), and the stop button (it is the red button on top of the editor pane). Alternatively, if you're using terminal, you can go the directory of your code using cd then your folder or directory first, like this:

cd Projects


once you are inside the directory then you can run your code using this command (If you have your code in your main directory, you can just directly use):

python name.py


HTML for the App

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Oxanium:wght@200..800&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
<title>Cyvercam</title>
</head>

<style>
*{
box-sizing: border-box;
margin: 0;
padding: 0;
}

body{
background-color: rgb(33, 33, 33);
font-family: 'Roboto Mono';

}

header{
background-color: rgb(53, 53, 53);
padding:20px
}

header h1{
font-family: 'Roboto Mono';
color:white;
}

section{
background-color: rgb(53, 53, 53);
padding:20px;
height: 20vh;
}

img{
width: 40vw;
border-style: solid;
border-width: 10px;

}

.buttons{
/* border-style: solid; */
display: flex;
justify-content: space-evenly;
width: 20vw;
}

#button{
color: black;
background-color: white;
border-style: none;
font-size: 3rem;
padding:10px;
border-radius: 80px;
width: 7.5vw;
height:15vh;
}

#button:active{
background-color: rgb(0, 187, 244);
}

#Off{
color:white;
position: relative;
bottom:250px;
z-index: -1;
}

.BS{
postion:relative;
bottom: 200px;
}

@media(max-width: 600px){
.buttons{
/* border-style: solid; */
display: flex;
justify-content: space-evenly;
width: 90vw;
}

img{
width:100vw;
}

#button{
border-style: none;
font-size: 3rem;
padding:10px;
border-radius: 80px;
width: 27vw;
}

#Off{
bottom:167px;

}


}

@media(max-width: 400px){
.buttons{
/* border-style: solid; */
display: flex;
justify-content: space-evenly;
width: 90vw;
}

img{
width:100vw;
}

#button{
border-style: none;
font-size: 3rem;
padding:10px;
border-radius: 80px;
width: 29vw;
}

#Off{
bottom:167px;

}


}

</style>

<header>
<center><h1>Cyvercam version 1.0</h1></center>

</header>

<body>

<center><img src="{{ url_for('video_feed')}}"></center>
<center><p id="Off">Powered off</p></center>


<section class = "BS">
<center><div class="buttons">
<button id="button" class="left" onclick="move('left')">&lt;</button>
<button id="button" class="right" onclick="move('right')">&gt;</button>
</div></center>
</section>

<script>
function move(direction){
fetch('/' + direction)
.then(response => {
if(!response.ok){
console.error('Failed to move:', response.status);
}

})
.catch(error => console.error('Error:', error));
}
</script>

</body>
</html>


Since we will be using a webserver for the app, we would need an HTML file.


I would recommend you watch this video to have a little bit of background and understanding with regard to using Flask. We will use this to run the video feed from the camera and our control interface for the servo through a web server.


create a folder in your Pi and name it templates. Make sure that it is in the same directory as your Python script. Name this as Cyver_index.html. You can change the name with anything you want, but make sure you also edit the one in the Python script above, mainly the lines where return render_template is being stated.


If you would prefer downloading the files, both are in my GitHub Repository.

Running the Code

For running the code, you we

Accessing the App

Screenshot 2025-08-01 140008.png
Screenshot 2025-07-31 145050.png

We're almost there, now we just have to access the app.


Accessing the app is as simple as just connecting to the same network your Pi is connected. Once you're connected, you can check your Pi's IP address in its terminal using the ifconfig command (it should be on the wlan section), or you can check the connected devices on your router by going to its admin page by entering its IP address into your web browser. It's either 192.168.1.1 or 192.168.0.1, but it really depends on your router (try checking the back of router, it's usually there). Another option is to download Angry IP Scanner, which is basically a program for your pc that scans the IP addresses connected to your local network. Now, with either of these methods, you should be able to identify your Pi's IP address by looking for its hostname. Here's a video for your reference on this method.


Once you got your Pi's IP address, just enter it on your web browser with its port as well. It would look like this:

IP address:5000


Final Notes

I still plan on implementing better updates to the code like controlling the servo using a scroll feature instead of a button, or I can keep the button and make it so that with every press, it increments its movement relative to where the button is directed. For now, I just kept it simple with a front and rear view feature.


I hope this guide was helpful to some of those who are new to Raspberry Pi just like me, and are interested to learn more. I believe that there are still a lot of developments that can be added to this project but I will leave it to you guys to explore further by yourselves to learn and gain more insights as well.


Thank you very much for reading!