Protecting From Bears

A Raspberry Pi Motion Detector with Azure Integration

After my webinar last week, lots of people have been asking about how I did the motion detection demo wherein I used a Raspberry Pi based motion detector that used the Azure IoT Hub and more broadly other Azure resources. The demo was a little cheeky because I used a princess castle, toy bear, and toy police officer as models to demo the app. In reality, the triviality of this means that the device itself could probably send the messages, but where’s the fun in that? Azure is awesome, and the Azure IoT is designed for scale…image thousands of devices doing this!

The Raspberry Pi setup was pretty simple, as it required no soldering, breadboards, capacitors, resistors or anything of that nature – just an old USB webcam that I had lying around. The Pi itself was connected to an Ethernet hub and powered by a USB phone charger. The principle purpose of this demo was not to have a complex IoT device, rather it was more intended to highlight the capabilities of Azure IoT and how to integrate it with other Azure resources.

The Raspberry Pi has a NodeJS script that acts as a wrapper around a little Linux utility called Motion, which can use all kinds of device and streams for motion detection. It does this by looking for differences in frames taken from a video steam. The script looks for the output from motion, which in this case are JPG images that are saved should motion be detected. Below are some sample images.

When images are found, these are uploaded via IoT Hub to an Azure Storage Account. Once a fair number of images are sent, the script then sends a message to IoT Hub. IoT Hub is wired up to an Azure Service Bus Message Queue which receives the message publications. An Azure Function subscribes to the queue to handle the messages. The function simply creates an email with references to the images embedded, and then sends that email message out by way of SendGrid. All in all, this simple demo shows end-to-end what an Azure IoT app might look like.

IoT App Architecture
IoT App Architecture

A few disclaimers: this is a demo and is by no means intended to be run as a production grade app. This code and setup are actually part of a larger project that I am working on that will involved AI and image recognition, but borrowed pieces from it to do an IoT demo. Also, This is not not a trivial setup, so it can take a while to do. Many of the details are glossed over in favor of brevity. If you have questions, please post in the comments or contact me. So with no further adieu, here’s how to set this up.

Create Resources

To create resources, follow the links below to Azure which have detailed instructions for how to create the necessary resources in the Azure Portal. Along with these links are some notes about what needs to be done on each of these to ensure that you get the right settings. Before creating all these resources it’s important to remember to get all of these resources on the same subscription, in the same resource group, and in the same region for best performance.

  1. Create a Function App. Choose Windows for the Type and for the Billing model, select Consumption Plan. You can take the defaults here, but for demo purposes you can turn off App Insights. Make a note of the Region, Subscription, and Resource group you are using..
  2. Create an IoT Hub in the same subscription, resource group, and region as you used for the Function App. Set the Pricing and scale to S1: Standard tier.
  3. Create a Service Bus in the same subscription, resource group, and region as you used for the Function App and use the Basic tier.
  4. Create a SendGrid account and select the Free tier.

Configure Resources

Once all of the resources are deployed…

  1. Configure SendGrid by opening the SendGrid resource in your resource group. Click on Manage, which will take you to the SendGrid website. Logon to SendGrid with your SendGrid username and password. Click Settings -> API Key -> Create API Key. Give the Key a name and then take the defaults. After this, copy the generated key and paste it somewhere. You’ll need it later.
  2. Configure the Service Bus by opening the Service Bus resource in your Resource group, then select + Queues. Create a queue from the Overview blade and make note of the queue name because you’ll need this later. The size and other settings can be left on their defaults.
  3. Configuring the IoT Hub has three things to configure.
    1. Configure a device by clicking on IoT devices -> Add. Give the device a Device ID and click Save. Make note of the the Device ID because you’ll need it later. Click on the device to pull up its details, then copy the Connection string (primary key) and paste it into a text editor. You’ll also need this later.
    2. Configure the Service Bus connection to IoT Hub by selecting Endpoints -> Add endpoint. Choose Service Bus Queue for the Endpoint type and give the endpoint a name. Choose the queue you created when you configured the Service Bus.
    3. Click the Files upload then choose the Storage account created by Function App and create a new storage container. Make a note of the container name because you’ll need this later.

Code the Function App

Now that the resources are configured, create a function in your function app to bring some of the resources together.

  1. Open the Function App you created.
  2. Click on the + icon next to Functions, then choose Create your own custom function.
  3. Choose JavaScript under Service Bus Queue Trigger.
  4. You can use whatever you want to for the Name. For the Service Bus connection, Click New, then choose the Service Bus you created. Type in the name of the queue you created Queue Name, and then click Create.
  5. After the function creates, select Integrate under the function, select + New Output, then select SendGrid.
  6. Supply an email address for the To address and the From address. For SendGrid API Key App Setting, click New, then use APIKey for the Key and the API Key you copied from SendGrid for the Value. Finally, click Save.
  7. Click on the name of the function to bring up the code editor and paste in the following code. Change the value of container to be the same as the container you created when you configured the File upload on the IoT Hub, then Save the code.
     var azure = require('azure-storage');
    
     module.exports = function(context, mySbMsg) {
    
         var blobService = azure.createBlobService(process.env.APPSETTING_AzureWebJobsStorage);
    
         var deviceId = mySbMsg.deviceId;
         var htmlstr = "<h1>" + deviceId + "</h1>"
         htmlstr += "<h3>" + mySbMsg.timestamp + "</h3>"
    
         var container = "motion-files"
    
         for(var i = 0; i < mySbMsg.files.length; i++){
             var dateNow = new Date(new Date().toUTCString());
    
             var file = deviceId + "/" + mySbMsg.files[i];
    
             var numberOfDaysToAdd =  365;
    
             var operation = azure.BlobUtilities.SharedAccessPermissions.READ
    
             var sharedAccessPolicy = {
               AccessPolicy: {
                 Protocols : "https",
                 Permissions: operation,
                 Start: dateNow.setDate(dateNow.getDate()),
                 Expiry: dateNow.setDate(dateNow.getDate() + numberOfDaysToAdd)
               },
             };      
    
    
             var sasToken = blobService.generateSharedAccessSignature(container, file, sharedAccessPolicy)
             var sasUrl = blobService.getUrl(container, file, sasToken); 
    
             htmlstr += "<div><img src='" + sasUrl + "'></div>"
    
         }
    
         context.log(htmlstr)
    
         context.done(null, {
             message: {
                 content: [{
                     type: 'text/html',
                     value: htmlstr
                 }]
             }
         });
    
    
     };
    
  8. Click on the Function App name (not the function itself), then click Platform Features, then Advanced Tools (Kudu). This will launch a new tab in your browser.
  9. In Kudu, select Debug console, then CMD.
  10. Browse to site -> wwwroot -> then the name of your function.
  11. Install the script dependencies for the function. You may see some red text with “WARN” messages. You can ignore these.
     npm install azure-storage
    

That’s all for configuring the Azure Function, now you can proceed to setting up the Raspberry Pi

Configure the Raspberry Pi

This project used a Raspberry Pi 2 running Ubuntu Core. Installing Ubuntu Core on a Raspberry Pi is pretty straight forward. Once Ubuntu Core is installed, connect to the Pi with an SSH client and run the following commands.

  1. Get root access
     sudo -i
    
  2. Update your Pi.
     apt-get update && apt-get upgrade
    
  3. Get NodeJS.
     curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
    
  4. Install NodeJS and Motion
     apt-get install -y nodejs motion
    
  5. Configure Motion.First, edit the motion.conf file.
     nano etc/motion/motion.conf
    

    Then, search (Ctrl + W) for target_dir and set the value to /home/ubuntu/iot/motion/. Save the file (Ctrl + O).

  6. Create some folders
     mkdir /home/ubuntu/iot /home/ubuntu/iot/motion
    
  7. Change directories to the iot directory
     cd /home/ubuntu/iot
    
  8. Create a script called iot.js
     nano iot.js
    
  9. Paste in the following code. Change <YOUR DEVICE CONNECTION STRING> to the connection string you copied when you configured the IoT Device and <YOUR DEVICE ID> to the Device ID. Then, save the File (Ctrl + O).
     var fs = require('fs');
     var { exec } = require('child_process');
     var clientFromConnectionString = require('azure-iot-device-mqtt').clientFromConnectionString;
     var Message = require('azure-iot-device').Message;
     var moment = require("moment");
    
     var connectionString = '<YOUR DEVICE CONNECTION STRING>';
     var deviceId = '<YOUR DEVICE ID>'
     var folder = '/home/ubuntu/iot/motion/'
    
    
     var client = clientFromConnectionString(connectionString);
    
     var pollFreq = 1000
    
     var connectCallback = function (err) {
         if (err) {
             console.error('Could not connect: ' + err);
         } else {
             console.log('Client connected');
             checkImages();
             client.on('message', function (msg) { 
                 var toggle = msg.data.toString('utf-8');
                 if(toggle == "ON"){
                     console.log("Motion Detection is ON");
                     exec("motion")
                 }else if(toggle == "OFF"){
                     console.log("Motion Detection is OFF");             
                     exec("pkill motion")
                 }
                 client.complete(msg, function () {              
                 });
             }); 
         }
     };
    
     client.open(connectCallback);
     console.log('Starting daemon...');
    
    
     function checkImages(){
         fs.readdir(folder, (err, files) => {
             var fileIdx = 0;
    
             if (files.length > 0){
                 var sendImage = function(){
                     var filename  = files[fileIdx];
                     fs.stat(folder + filename, function (err, stats) {
                     var rr = fs.createReadStream(folder + filename);
                         client.uploadToBlob(filename, rr, stats.size, function (err) {
                             if (err) {
                                 console.error('Error uploading file: ' + err.toString());
                             } else {
                                 fs.unlinkSync(folder + filename);
                                 console.error('Uploaded file: ' + filename);
                             }
                             fileIdx++;
                             if (fileIdx == files.length){
                                 var now = moment()
    
                                 data = JSON.stringify({ deviceId: deviceId, timestamp: now, files: files });
                                 message = new Message(data);
                                 message.contentEncoding = "utf-8";
                                 message.contentType = "application/json"
                                 console.log("Sending message: " + message.getData());
                                 client.sendEvent(message);
                                 setTimeout(function(){checkImages();}, pollFreq);
                             }else{
                                 sendImage();
                             }                           
                         });
                     });
                 }
                 sendImage();            
             }else{
    
    
             }
         })  
     }
    
  10. Install the app dependencies.
     npm install azure-iot-device moment
    

The Raspberry Pi is configured to run at this point.

Start Motion Detection

  1. Start the script.
     node iot.js
    
  2. Back in the Azure Portal, select your Resource group, then your IoT Hub, select you Device, then Message to Device.
  3. Type in ON in the Message Body, then click Send Message. (Conversely, you can turn off motion detection by sending an OFF message.)
  4. Back in the console running the IoT Script, you should see a message indicating that motion detection is turned on.
     Motion Detection is ON
    
  5. Place an object in front of the connected camera. You should see activity in the console with images and messages being sent to the IoT Hub.
  6. If everything is working right, you should start getting message in your inbox with images take by the camera!

0 comments on “A Raspberry Pi Motion Detector with Azure IntegrationAdd yours →

Leave a Reply

Your email address will not be published. Required fields are marked *

5 × three =

This site uses Akismet to reduce spam. Learn how your comment data is processed.