Detecting Motion Using A Raspberry Pi, Adafruit Motion Sensor, And NodeJS

I am working on a side project which involves a Raspberry Pi (model 2B) and an Adafruit PIR motion sensor. I decided early on that I do not want to learn and use Python, the more “native” programming language usually used to interact with the Raspbian OS. Rather, I would use NodeJS, the server-side language that uses the javascript syntax. I’ve been wanting to learn Node and know the basics of the Javascript language already, so I figured it was the perfect opportunity to dive in. I connected the ground, power, and lead wires of the motion detector shield to the respective GPIO pins and powered up my Pi.

One thing I quickly discovered about NodeJS is that the community that contributes to it is much like the community that supports javascript: there are countless open source libraries out there to aid in your project. I found the NodeJS library Pi-GPIO in order to interface with my analog motion detector. I am also using the UUID library to create a new identifier string to send to my API when a motion is detected on the Pi.

GPIO Issues

After a few frustrating hours tinkering and failing with the pi-gpio library, I decided to learn more about the pins themselves. GPIO pins on the Pi are rated 3.3volts and must be configured to a read or write state through the operating system before use through code. There is a directory created for each pin that writes, or reads, from a specified file for that pin. Zero for off, one for on.

For example, I have my motion detector’s lead wire connected to GPIO pin 18 on my Pi. I turn this pin on to a “read” mode by typing this into the console:

echo 18 > /sys/class/gpio/export

This then creates the directory “/sys/class/gpio/gpio18/” with helpful files that relate to the current state of the motion detection shield. The “value” file is what the pi-gpio NodeJS library requires to function. When motion is detected by the sensor, this file has a singular “1” written to it. When no motion is detected, or when motion stops, a “0” is the only contents of the “value” file.

The Code

I have more than motion detection going on in my project, so I wanted to create a standalone library to control just the motion detection piece. The main program would create a new motion detection client, and use it as necessary.

const motionDetectorLib = require('./lib/motiondetection');

const MyProject = function(){
    const myProject = this;
    
    myProject.init();
};

MyProject.prototype.init = function() {
    const myProject = this;

    myProject.motionDetectorClient = new motionDetectorLib.motionDetectionClient(myProject.onMotionDetected.bind(myProject));
};

MyProject.prototype.onMotionDetected = function() {
    const myProject = this;
    
    //motion has been detected, do something!
}

Being a javascript noobie (this is the first time I’ve used .prototype) I quickly learned that my callback “onMotionDetected” was not keeping the current “this” state when the callback occurred. Although I still don’t understand why I need to do this, javascript and NodeJS come with a built in .bind function that will staple an object and context together, so that when the callback occurs, the “this” state contains all the expected values.

myProject.motionDetectorClient = new motionDetectorLib.motionDetectionClient(myProject.onMotionDetected.bind(myProject));

Motion Detection Library

I wanted motion capture to be more than a binary state in my project. A “capture” consists of a motion detection event that could last for a single instant, or for a few seconds after that. It would also include a timeout so that motion detection couldn’t be constant, but broken up into chunks of “captures”.

This idea is supported by several properties on the library like: “timeoutBeforeNextCaptureInSeconds”, “dateBeforeNextCaptureCanStart”, and “secondsMaxForCapture”.

My full custom motion detection library can be found below:

const utils = require("./utils")
const gpio = require("pi-gpio")
const uuid = require("uuid/v1")

const MotionDetectionClient = function(_motionDetectedCallback){
    const client = this
    
    //triggering MD takes this long before the sensor will turn "off"
    //this seems to be a limit of the hardware MD sensor, find a better one??
    client.minMotionDetectionTimeInSeconds = 5
    client.timeoutBeforeNextCaptureInSeconds = 20
    client.dateBeforeNextCaptureCanStart = new Date()
    client.checkMotionDetectorIntervalInMs = 1000
    
    //Pi2 model B, pin 12 here = 18 on the PI itself
    //echo 18 > /sys/class/gpio/export
    //https://github.com/JamesBarwell/rpi-gpio.js
    //http://www.raspberrypi-spy.co.uk/2012/06/simple-guide-to-the-rpi-gpio-header-and-pins/
    client.gpioPinNumber = 12;
    
    client.motionDetectedCallback = _motionDetectedCallback;
    
    client.currentCapture = {}
    
    client.init()
};

//capture object - this represents a span of time when the motion detector is active
MotionDetectionClient.prototype.Capture = function() {
        
    const state = this
    
    //sent to API
    state.uuid = uuid()

    state.startTime = new Date()
    state.secondsMaxForCapture = 3;
    
    state.maxEndTime = new Date();
    state.maxEndTime.setSeconds(state.maxEndTime.getSeconds() + state.secondsMaxForCapture);
    
    state.isActive = function() {
        const nowDate = new Date()
        
        return nowDate <= state.maxEndTime;
    }
};

MotionDetectionClient.prototype.init = function() {
    const client = this
    
    console.log("connecting motion detection to GPIO pin: " + client.gpioPinNumber)
    
    //apparently need an anon function to keep "this" scope
    setInterval((() => client.readInput()), client.checkMotionDetectorIntervalInMs)
}

MotionDetectionClient.prototype.readInput = function() {
    const client = this
    
    gpio.read(client.gpioPinNumber, function(err, value) {
        if(err)
        {
            console.log(err);
            throw err;
        }
        
        if (value === 1)
        {
            //internal logic
            client.OnCaptureMotion();
        }
    });    
}

MotionDetectionClient.prototype.OnCaptureMotion = function() {
    const client = this;
    
    const nowDate = new Date()

    if (utils.isEmptyObject(client.currentCapture) || !client.currentCapture.isActive())
    {
        //timout so it can"t be constantly recording without breaks
        if (nowDate < client.dateBeforeNextCaptureCanStart)
        {
            console.log("timeout on motion detection");
            return;
        }
        
        client.currentCapture = new client.Capture();
        client.dateBeforeNextCaptureCanStart = nowDate;
        client.dateBeforeNextCaptureCanStart.setSeconds(nowDate.getSeconds() + client.timeoutBeforeNextCaptureInSeconds)
        
        console.log("new capture")
    }
    else
    {
        console.log("continuing capture")
    }
    
    //external logic - note caller had to use function.bind here to keep state
    client.motionDetectedCallback();
}

module.exports = {
    motionDetectionClient: MotionDetectionClient
};

Leave a Reply

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