j3lte/node-motion

View on GitHub
motion/index.js

Summary

Maintainability
A
1 hr
Test Coverage
'use strict';
/*
 * node-motion
 * https://github.com/j3lte/node-motion
 *
 * Copyright (c) 2015 Jelte Lagendijk
 * Licensed under the MIT license.
 */
var spawn = require('child_process').spawn;
var _ = require('lodash');

var shell = require('shelljs');

var Emitter = require('events').EventEmitter;
var util = require('util');

var supportedVersions = [
  '3_2_12',
  '3_2_12_git20140228'
];

var Motion = function(path) {

  if (path && !shell.test('-e', path)) {
    shell.echo('Sorry, this module cannot find motion on given path: ' + path);
    process.exit(1);
  } else if (!shell.which('motion')) {
    shell.echo('Sorry, this module requires Motion to be installed');
    process.exit(1);
  }

  this.motionBin = path || 'motion';
  this.version = this.getVersion();

  if (!_.includes(supportedVersions, this.version)) {
    shell.echo('Sorry, motion version ' + this.version + ' is not supported');
    process.exit(1);
  }

  this.configFile = null;
  this.motion = null;

  this.httpBackendStarted = false;
  this.streamStarted = false;

  this.messageArray = [
    {version: '3_2_12', regex: /^motion-httpd: waiting for data on port TCP (\d+)/, action: 'backend'},
    {version: '3_2_12', regex: /^Started stream webcam server in port (\d+)/, action: 'stream'},
    {version: '3_2_12_git20140228', regex: /httpd_run: motion-httpd: waiting for data on (localhost|\d\.\d\.\d\.\d) port TCP (\d+)/, match: 2, action: 'backend'},
    {version: '3_2_12_git20140228', regex: /motion_init: Started motion-stream server in port (\d+).*$/, action: 'stream'}
  ];
};

util.inherits(Motion, Emitter);

Motion.prototype.getVersion = function(){
  var helpCmd = this.motionBin + ' -h | head -n 1';
  var versionRegEx = /^motion Version ([\d\.\+a-z]+), .*$/;
  var version = shell.exec(helpCmd, {silent: true}).output.replace(/\n/, '');
  if (version === '') {
    shell.echo('Sorry, cannot determine Motion version for given Motion binary on : ' + this.motionBin);
    process.exit(1);
  } else {
    var res = versionRegEx.exec(version);
    if (res) {
      return res[1].replace(/\./g, '_').replace(/\+/g, '_');
    } else {
      shell.echo('Sorry, cannot determine Motion version, or version is not supported');
      process.exit(1);
    }
  }
  return null;
};

Motion.prototype.setConfig = function(configFile) {
  if (!configFile) {
    return;
  }
  if (!shell.test('-e', configFile)) {
    shell.echo('Sorry, the provided configuration file cannot be found : ' + configFile);
    //process.exit(1);
  } else {
    this.configFile = configFile;
  }
};

Motion.prototype.start = function(){
  var that = this;
  that.stop();
  if (!that.configFile) {
    return;
  }
  var args = [
    '-n',
    '-d', '6',
    '-c', that.configFile
  ];
  this.motion = spawn(this.motionBin, args);
  this.motion.stdout.on('data', function (data) {
    that.processData(data);
  });
  this.motion.stderr.on('data', function (data) {
    that.processData(data);
  });
  this.motion.on('close', function (code) {
    that.emit('exit', 'child process exited with code ' + code);
  });
};

Motion.prototype.stop = function() {
  if (this.motion) {
    this.motion.kill();
  }
};

Motion.prototype.running = function(){
  if (this.motion) {
    return true;
  }
  return false;
};

Motion.prototype.checkMessages = function(msg) {
  var that = this;
  var matched = false;
  var payload = null;
  _.forEach(that.messageArray, function (message) {
    if (message.version !== that.version) {
      return;
    }
    if (message.msg && msg.indexOf(message.msg) !== -1) {
      matched = message.action;
    }
    if (message.regex) {
      var res = message.regex.exec(msg);
      if (res) {
        matched = message.action;
        var match = message.match || 1;
        payload = res[match];
      }
    }
  });
  switch(matched) {
  case false:
    break;
  case 'backend':
    that.httpBackendStarted = payload;
    that.emit('msg', { action: matched, value: payload });
    break;
  case 'stream':
    that.streamStarted = payload;
    that.emit('msg', { action: matched, value: payload });
    break;
  default:
    break;
  }
};

Motion.prototype.processData = function(data) {
  var that = this;
  var regex = /^(\[[\dA-Z]+\]\ )+(.*)$/g;
  var strings = data.toString('ascii').split('\n');

  var msgs = _.chain(strings)
    .filter(function(msg) {
      return msg !== '';
    })
    .map(function (line) {
      return line.replace('\t', '').replace(regex, '$2');
    })
    .forEach(that.checkMessages.bind(that))
    .forEach(function (line) {
      that.emit('debug', line);
    })
    .value();

  return msgs;
};

module.exports = Motion;