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
};