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