Skip to content

Commit

Permalink
Added README and simplified throughout
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickfuller committed Jun 3, 2014
1 parent c50116a commit 2afa3c4
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 58 deletions.
92 changes: 91 additions & 1 deletion README.md
@@ -1,4 +1,94 @@
campi
=====

Another Raspberry Pi Camera Webserver
Another Raspberry Pi camera webserver.

![](img/campi.png)

What it does
============

Hosts a website where you can view your webcam in real time.

Why I wrote it
==============

There are a *lot* of tutorials out there on how to turn your pi into a webcam
server. Most of them involve installing [motion](http://www.lavrsen.dk/foswiki/bin/view/Motion),
which works great in many use cases. However, I wanted something simpler. Namely,
I wanted:

* Minimal configuration
* Password protection
* One-way streaming
* Easily customizable webpage
* Extensible server

Campi does just this. Nothing else.

Installation
============

Campi uses Python, [tornado](http://www.tornadoweb.org/en/stable/) to create a
web server, and [opencv](http://opencv.org/) to interface with the webcam.
[Pillow](http://pillow.readthedocs.org/en/latest/installation.html) simplifies
image format conversion.

```
sudo apt-get install python-dev python-pip python-opencv
sudo pip install tornado Pillow
```

Once the dependencies are installed on your pi, you can clone this repository and
run the server.

```
git clone https://github.com/patrickfuller/campi.git
cd campi
python server.py
```

That's it. Navigate to http://your.r.pi.ip:8000 and check out your webcam.

####Password

The default password is "raspberry". In order to change it, run this in your
campi directory:

```
python -c "import hashlib; import getpass; print(hashlib.sha512(getpass.getpass())).hexdigest()" > password.txt
```

This will prompt you for a password, encrypt it, and save the result in
`password.txt`.

Note that this level of password protection is basic - it's fine for keeping the
occasional stranger out, but won't stand up to targeted hacking.

####Run on startup

It's nice to have your pi start campi whenever it turns on. Let's make that
happen.

```
sudo sh -c 'echo "#!/bin/sh
nohup python /home/pi/campi/server.py &" > /etc/init.d/campi'
sudo chmod +x /etc/init.d/campi
```

This creates the file `/etc/init.d/campi`, fills it with a command that runs
the server, and then sets it to executable. Note that you may need to change the
path (`/home/pi/campi/server.py`) to point to the right file.

####Customization

The website consists of `index.html`, `login.html`, and `style.css`. These can be
edited to change the look of campi.

If you want to add in extra functionality (like a slider for frame rate), edit
`client.js` and `server.py`. The client should send a request to the server, which
will then cause the server to do something.

If you want to add in extra camera features, opencv comes with a lot of useful
computer vision algorithms. Check out its functionality before writing your
own.
4 changes: 2 additions & 2 deletions client.js
Expand Up @@ -8,7 +8,7 @@ var client = {

// Connects to Pi via websocket
connect: function (port) {
var self = this, video = document.getElementById(".video");
var self = this, video = document.getElementById("video");

this.socket = new WebSocket("ws://" + window.location.hostname + ":" + port + "/websocket");

Expand All @@ -27,6 +27,6 @@ var client = {

// Requests video stream
readCamera: function () {
this.socket.send(JSON.stringify({method: "read_camera", frame_rate: 5}));
this.socket.send(JSON.stringify({method: "read_camera", frame_rate: 15}));
}
};
11 changes: 4 additions & 7 deletions index.html
Expand Up @@ -12,10 +12,10 @@
<body>
<div class="container">
<div class="row top">
<div class="col-xs-2">
<img src="static/img/campi.png">
<div class="col-xs-1">
<img class="logo-small" src="static/img/campi.png">
</div>
<div class="col-xs-10">
<div class="col-xs-11">
<h1>campi</h1>
<p>
An extensible webcam server for your Pi.
Expand All @@ -25,15 +25,12 @@ <h1>campi</h1>

<div class="row">
<div class="col-xs-12">
<img class="video"></img>
<img id="video"></img>
</div>
</div>
</div>

<script src="static/lib/jquery-1.10.2.min.js" type="text/javascript"></script>
<script src="static/lib/bootstrap.min.js" type="text/javascript"></script>
<script src="static/client.js" type="text/javascript"></script>

<script type="text/javascript">
// Connect a websocket to the server. The port notation is post-
// processed server-side according to the config
Expand Down
19 changes: 7 additions & 12 deletions login.html
Expand Up @@ -3,24 +3,19 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NuMat Login</title>
<title>Campi Login</title>

<link href="http://fonts.googleapis.com/css?family=Lato:400,700" rel="stylesheet" type="text/css" />
<link href="static/lib/bootstrap.css" rel="stylesheet" type="text/css" />
<link href="static/style.css" rel="stylesheet" type="text/css" />
</head>

<body>
<div class="container cell-table">
<div class="row top cell">
<div class="col-xs-12">
<form action="/login" method="POST" class="login-form">
<img class="login-img" src="static/img/campi.png"></img>
<p><input type="password" name="password" /><p>
<p><button class="login-button" type="submit">Log In</button></p>
</form>
</div>
</div>
<div class="centered">
<form action="/login" method="POST">
<img class="logo" src="static/img/campi.png"></img>
<p><input type="password" name="password" /><p>
<p><button type="submit">Log In</button></p>
</form>
</div>
</body>
</html>
Expand Down
1 change: 1 addition & 0 deletions password.txt
@@ -0,0 +1 @@
5895bb1bccf1da795c83734405a7a0193fbb56473842118dd1b66b2186a290e00fa048bc2a302d763c381ea3ac3f2bc2f30aaa005fb2c836bbf641d395c4eb5e
16 changes: 9 additions & 7 deletions server.py
Expand Up @@ -20,10 +20,9 @@
camera = cv2.VideoCapture(0)

# Hashed password for comparison and a cookie for login cache
PASSWORD = ("fcb109fa2283b3ba51640e3c93b0307ac6332e5c1434d1b330be513373e65480"
"8223797aab1c09be4feba61cc06f2569a93c314e74052f6098b5b8331256967a")
COOKIE_NAME = "labcamera"
COOKIE_SECRET = "3RYBOfCdQpevL/OqUK8El1xZgHwQJUcsuOpBR1DY06o="
with open("password.txt") as in_file:
PASSWORD = in_file.read().strip()
COOKIE_NAME = "campi"


class IndexHandler(tornado.web.RequestHandler):
Expand Down Expand Up @@ -72,20 +71,23 @@ def loop(self):
_, frame = camera.read()
img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
img.save(sio, "JPEG")
self.write_message(base64.b64encode(sio.getvalue()))
try:
self.write_message(base64.b64encode(sio.getvalue()))
except tornado.websocket.WebSocketClosedError:
self.camera_loop.stop()


parser = argparse.ArgumentParser(description="Starts a webserver that "
"connects to a webcam.")
parser.add_argument("--port", type=int, default=16000, help="The "
parser.add_argument("--port", type=int, default=8000, help="The "
"port on which to serve the website.")
args = parser.parse_args()

handlers = [(r"/", IndexHandler), (r"/login", LoginHandler),
(r"/websocket", WebSocket),
(r'/static/(.*)', tornado.web.StaticFileHandler,
{'path': os.path.normpath(os.path.dirname(__file__))})]
application = tornado.web.Application(handlers, cookie_secret=COOKIE_SECRET)
application = tornado.web.Application(handlers, cookie_secret=PASSWORD)
application.listen(args.port)

webbrowser.open("http://localhost:%d/" % args.port, new=2)
Expand Down
53 changes: 24 additions & 29 deletions style.css
Expand Up @@ -4,37 +4,26 @@ body {

font-family: 'Lato', sans-serif;
font-weight: 400;
font-size: 16px;
}
body p {
font-size: 16px;
line-height: 1.32;
}

h1 {
margin-top: 0px;
font-weight: 700;
}

img {
width: 100%;
}

.row {
margin-bottom: 50px;
p {
font-size: 16px;
}

.top {
margin-top: 40px;
img {
width: 100%;
}


input,
button {
height: 35px;
width: 150px;
font-size: 15px;
margin-top: 0px;
font-size: 16px;
}

button {
Expand All @@ -44,8 +33,8 @@ button {

cursor: pointer;
color: #fff;
background: #393294;
text-align: left;
background: #bc1c42;
text-align: center;

-webkit-transition: 0.15s background ease;
-moz-transition: 0.15s background ease;
Expand All @@ -54,24 +43,30 @@ button {
transition: 0.15s background ease;
}
button:hover, button:active {
background: #54a;
background: #d25;
}

.cell-table {
display: table;
height: 100%;
.centered {
position: fixed;
margin-top: 10%;
left: 50%;
margin-left: -75px;
}

.cell {
display: table-cell;
vertical-align:middle;
.logo {
width: 150px;
margin-bottom: 25px;
}

.login-form, .login-button {
text-align: center;
.logo-small {
width: 75px;
}

.login-img {
width: 150px;
.top {
margin-top: 50px;
}

#video {
margin-top: 25px;
margin-bottom: 25px;
}

0 comments on commit 2afa3c4

Please sign in to comment.