Because Windows 10 IoT Core is not supporting the Pi Camera, I can only use Linux instead. Recently I made a surveillance camera. The functionality is simple; when a person passes by the camera, it will take a picture and upload it to Microsoft Azure Storage. It also has a website to remotely view the photos.
1. Hardware- PIR sensor
- Pi Camera
- a couple of jump wires
Connect the signal port of the PIR sensor to GPIO 04 and install the camera into CSI interface on the Raspberry Pi.
For installing the camera, please refer to official document here.
2. Working with PIR Sensor and CameraSSH into Raspbian, create a new Python file:
sudo nano pircam.py
The API for the PIR sensor is built into the Raspbian system. We only need to use it directly:
from gpiozero import MotionSensor pir = MotionSensor(4)
To test if a person is passing by the PIR sensor:
pir.wait_for_motion()
To test if no one is standing in front of the PIR Sensor:
pir.wait_for_no_motion()
The camera API is also already in place.
from picamera import PiCamera camera = PiCamera()
To take a photo:
camera.capture(filename)
So, to take a photo when a person is passing by the PIR sensor, we could do it this way:
from gpiozero import MotionSensor
from picamera import PiCamera
from datetime import datetime
camera = PiCamera()
pir = MotionSensor(4)
while True:
pir.wait_for_motion()
filename = "pircam-" + datetime.now().strftime("%Y-%m-%d_%H.%M.%S.jpg")
camera.capture(filename)
pir.wait_for_no_motion()
Save the python file, and execute the script:
sudo python pircam.py
If everything is good, you can cross your fingers before the PIR sensor, the camera will take a photo. You can use "ls" command to verify if the photo has been saved to disk.
3. Configuration of Azure Storage Account and How to Upload PhotoPlease refer to my post Uploading Pi Camera images to Azure. Finally the working code is:
from gpiozero import MotionSensor
from picamera import PiCamera
from datetime import datetime
from azure.storage.blob import BlockBlobService
from azure.storage.blob import ContentSettings
camera = PiCamera()
pir = MotionSensor(4)
block_blob_service = BlockBlobService(account_name='YOURACCOUNT', account_key='YOURKEY')
while True:
pir.wait_for_motion()
filename = "pircam-" + datetime.now().strftime("%Y-%m-%d_%H.%M.%S.jpg")
camera.capture(filename)
pir.wait_for_no_motion()
block_blob_service.create_blob_from_path(
'YOURCONTAINER',
filename,
filename,
content_settings=ContentSettings(content_type='image/jpeg'))
If nothing goes wrong, after executing the script, you will see the photos in your Azure Portal:
Create a new ASP.NET MVC5 project with VS2015. Naming it like "PirCamWatcher", update all nuget packages to latest version. Install Azure Storage SDK:
PM > Install-Package WindowsAzure.Storage
Modify web.config, add the following keys into appSettings node:
<add key="StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=YOURACCOUNT;AccountKey=YOURKEY" />
<add key="AzureStorageAccountContainer" value="YOURCONTAINER" />
<add key="AuthUserName" value="YOURUSERNAME" />
<add key="AuthPassword" value="YOURPASSWORD"/>
Please modify the values according to your own Azure environment.
The next thing is to make an authentication module. Because we are publishing it to the Internet, we can't allow everyone to see our photos. I choose Basic Authentication because it's the most simple way to do. To get it done in ASP.NET, we need to create an HttpModule first: e.g. BasicAuthHttpModule.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.Configuration;
namespace PirCamWatcher.IISModules
{
public class BasicAuthHttpModule : IHttpModule
{
private const string Realm = "PirCam";
public void Init(HttpApplication context)
{
// Register event handlers
context.AuthenticateRequest += OnApplicationAuthenticateRequest;
context.EndRequest += OnApplicationEndRequest;
}
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
private static bool CheckPassword(string username, string password)
{
var u = WebConfigurationManager.AppSettings["AuthUserName"];
var p = WebConfigurationManager.AppSettings["AuthPassword"];
return username == u && password == p;
}
private static void AuthenticateUser(string credentials)
{
try
{
var encoding = Encoding.GetEncoding("iso-8859-1");
credentials = encoding.GetString(Convert.FromBase64String(credentials));
int separator = credentials.IndexOf(':');
string name = credentials.Substring(0, separator);
string password = credentials.Substring(separator + 1);
if (CheckPassword(name, password))
{
var identity = new GenericIdentity(name);
SetPrincipal(new GenericPrincipal(identity, null));
}
else
{
// Invalid username or password.
HttpContext.Current.Response.StatusCode = 401;
}
}
catch (FormatException)
{
// Credentials were not formatted correctly.
HttpContext.Current.Response.StatusCode = 401;
}
}
private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeaderVal.Scheme.Equals("basic",
StringComparison.OrdinalIgnoreCase) &&
authHeaderVal.Parameter != null)
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
}
// If the request was unauthorized, add the WWW-Authenticate header
// to the response.
private static void OnApplicationEndRequest(object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.StatusCode == 401)
{
response.Headers.Add("WWW-Authenticate",
string.Format("Basic realm=\"{0}\"", Realm));
}
}
public void Dispose()
{
}
}
}
And then, configure it into Web.config:
<system.webServer>
<modules>
<add name="BasicAuthHttpModule"
type="PirCamWatcher.IISModules.BasicAuthHttpModule, PirCamWatcher"/>
</modules>
</system.webServer>
Open IIS, disable "Anonymous Authentication":
Now, try to access the website; it should ask you for a username and password:
Finally, because we are deploying it to Azure, we can not operate the IIS on Azure directly, so we need a tricky way to do it:
Modify Web.Release.config, add the following code:
<system.webServer>
<security xdt:Transform="Insert">
<authentication>
<anonymousAuthentication enabled="false" />
</authentication>
</security>
</system.webServer>
Open HomeController.cs, replace the code with this:
using System.Linq;
using System.Web.Configuration;
using System.Web.Mvc;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
namespace PirCamWatcher.Controllers
{
public class HomeController : Controller
{
public static CloudBlobContainer BlobContainer { get; set; }
public HomeController()
{
BlobContainer = GetBlobContainer();
}
public ActionResult Index()
{
return View();
}
public JsonResult GetImages()
{
var listBlobProperties = (from item in BlobContainer.ListBlobs(null, false)
where item.GetType() == typeof(CloudBlockBlob)
select (CloudBlockBlob)item
into blob
select new Models.PirCamBlobProperties(blob.Properties.LastModified, blob.Uri))
.OrderByDescending(p => p.DateModified)
.ToList();
return Json(listBlobProperties, JsonRequestBehavior.AllowGet);
}
private static CloudBlobContainer GetBlobContainer()
{
string connectionString = WebConfigurationManager.AppSettings["StorageConnectionString"];
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container =
blobClient.GetContainerReference(WebConfigurationManager.AppSettings["AzureStorageAccountContainer"]);
return container;
}
}
}
Add a "PirCamBlobProperties.cs" into Models:
using System;
namespace PirCamWatcher.Models
{
public class PirCamBlobProperties
{
public DateTimeOffset? DateModified;
public Uri Uri;
public PirCamBlobProperties(DateTimeOffset? dateModified, Uri uri)
{
DateModified = dateModified; Uri = uri;
}
}
}
Open Home\Index.cshtml, replace the code with:
@{
ViewBag.Title = "Home";
}
@section scripts{
<script>
function showImage(uri) {
$(".img-prev-lg").html("<img data-original='" + uri + "' class='img-responsive' />");
$("#img-modal").modal();
}
$(function () {
$.getJSON("Home/GetImages",
function (data) {
var items = [];
$.each(data, function (k, v) {
items.push(
"<div class='sv-img' data-imgurl='" + v.Uri + "'>" +
"<img data-original='" + v.Uri + "' class='img-responsive' />" +
"<div class='sv-date'>" + new Date(parseInt(v.DateModified.substr(6))) + "</div>" +
"</div>");
});
$("<div />", {
"class": "img-container",
html: items.join("")
}).appendTo(".gallery-display");
}).done(function () {
$(".sv-img").click(function () {
var uri = $(this).data('imgurl');
showImage(uri);
});
});
});
</script>
}
<h1 class="page-header">
Azure Home Surveillance
<small>Raspberry Pi PIR Motion Camera</small>
</h1>
<section class="gallery-display"></section>
<div id="img-modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content img-prev-lg">
</div>
</div>
</div>
Modify site.css, add code:
.sv-img {
display: inline-block;
width: 275px;
margin: 0 10px 10px 0;
border: 1px solid #CCC;
position: relative;
cursor: pointer;
}
.sv-date {
color: #FFF;
background-color: #000;
-ms-opacity: 0.5;
opacity: 0.5;
position: absolute;
bottom: 0;
padding: 5px;
}
Publish the website to Azure, and you are good to go!
The entire project is on my Github.
Comments