From 094eabae5397f30dc3f007c4d3c72bb0f00edd69 Mon Sep 17 00:00:00 2001 From: fegonda Date: Mon, 18 Jul 2016 19:59:32 +0000 Subject: [PATCH 1/4] changes to support reading data from web cam and flash with led --- app/connectors/lepton-pan-tilt-connector.js | 239 +++++++++++++++----- config/config.json | 58 +++-- data/README.md | 1 + 3 files changed, 225 insertions(+), 73 deletions(-) mode change 100755 => 100644 config/config.json create mode 100644 data/README.md diff --git a/app/connectors/lepton-pan-tilt-connector.js b/app/connectors/lepton-pan-tilt-connector.js index 238494f..40aaf39 100755 --- a/app/connectors/lepton-pan-tilt-connector.js +++ b/app/connectors/lepton-pan-tilt-connector.js @@ -1,21 +1,26 @@ /* jshint node:true */ 'use strict'; - +var _path = require('path'); +var _fs = require('fs'); var _shortId = require('shortid'); var _spi = require('spi'); var _util = require('util'); var _q = require('q'); var _wiringPi = require('../utils/wiring-pi-wrapper'); var PollingConnector = require('iot-client-lib').PollingConnector; +var spawn = require('child_process').spawn; var PACKET_SIZE = 164; var PACKETS_PER_FRAME = 60; var DEFAULT_MAX_RETRIES = 750; var DEFAULT_CAMERA_RESET_PIN = 23; //wiring pi pin number +var DEFAULT_CAMERA_LIGHTS_PIN = 4; //wiring pi pin number var DEFAULT_SCANUPDATE_FREQ = 50; var DEFAULT_RECAPTURE_MAXTRIES = 20; var DEFAULT_RECAPTURE_DELAY = 500; - +var RGB_MAX_READ_RETRIES = 5; +var RGB_PATH_PREFIX = '../../data/rgb_'; + function Servo( config ) { this.config = config; this.reset(); @@ -28,6 +33,7 @@ Servo.prototype.reset = function() { this.callback = null; this.index = 0; this.indexIncrement = 1; + this.rgbRetries = RGB_MAX_READ_RETRIES; }; Servo.prototype.move = function(angle, callback) { @@ -91,7 +97,9 @@ function LeptonPanTiltCameraConnector(id) { this._camera = null; this._scanning = false; - this._scanId = 0; + this._scan = 0; + this._row = 0; + this._col = 0; this._scanUpdateTimer = null; this._recaptureRetries = 0 this._tiltServo = null; @@ -110,15 +118,15 @@ _util.inherits(LeptonPanTiltCameraConnector, PollingConnector); * @private */ LeptonPanTiltCameraConnector.prototype._resetCamera = function() { - var camera = this._camera; - this._camera = null; + //var camera = this._camera; + //this._camera = null; this._logger.info('Starting camera reset'); _wiringPi.digitalWrite(this._config.cameraResetPin, 0); setTimeout(function() { this._logger.info('Camera reset complete'); _wiringPi.digitalWrite(this._config.cameraResetPin, 1); - this._camera = camera; + //this._camera = camera; }.bind(this), 500); }; @@ -152,6 +160,11 @@ LeptonPanTiltCameraConnector.prototype._start = function() { this._config.cameraResetPin = DEFAULT_CAMERA_RESET_PIN; } + if(typeof this._config.cameraLightsPin !== 'number' || + this._config.cameraLightsPin <= 0) { + this._config.cameraLightsPin = DEFAULT_CAMERA_LIGHTS_PIN; + } + if(this._config.tiltServo == null) { def.reject('tiltServo settings not specified'); return; @@ -203,6 +216,7 @@ LeptonPanTiltCameraConnector.prototype._start = function() { //_wiringPi.setup('wpi'); _wiringPi.pinMode( this._config.tiltServo.pin, _wiringPi.PWM_OUTPUT ); _wiringPi.pinMode( this._config.panServo.pin, _wiringPi.PWM_OUTPUT ); + _wiringPi.pinMode( this._config.cameraLightsPin, _wiringPi.OUTPUT ); _wiringPi.pwmSetMode( _wiringPi.PWM_MODE_MS ); _wiringPi.pwmSetClock( 375 ); _wiringPi.pwmSetRange( 1024 ); @@ -246,6 +260,8 @@ LeptonPanTiltCameraConnector.prototype._stop = function() { this._logger.info('Stopping connector'); var def = _q.defer(); try { + this._setEnableLights( false ); + if(this._camera) { this._logger.info('Closing camera on: [%s]', this._config.spiDevice); // NOTE: This is a synchronous (blocking) call. @@ -326,7 +342,8 @@ LeptonPanTiltCameraConnector.prototype._pan = function() { LeptonPanTiltCameraConnector.prototype._panFinished = function() { this._logger.info('_panFinished - ' + this._panServo.index + '/' + this._panServo.angle); - this._tryCapture(); + //this._tryCapture(); + this._capture(); } /** @@ -334,33 +351,54 @@ LeptonPanTiltCameraConnector.prototype._panFinished = function() { * @method _tryCapture * @protected */ -LeptonPanTiltCameraConnector.prototype._tryCapture = function() { +LeptonPanTiltCameraConnector.prototype._capture = function() { + + var pantilt = this; + + if (this._camera == null) return; var tIndex = this._tiltServo.index; var pIndex = this._panServo.index; var tAngle = this._tiltServo.angle; var pAngle = this._panServo.angle; - this._logger.info('_tryCapture - tilt: '+ tIndex + '/' + tAngle + ' pan: ' + pIndex + '/' + pAngle ); + this._logger.info('_capture - tilt: '+ tIndex + '/' + tAngle + ' pan: ' + pIndex + '/' + pAngle ); - if (this._captureRetries == this._config.recaptureMaxRetries) { - this._logger.info('Maximum recapture retries reached - aborting scan.'); - this._abortScan(); - return; - } + this._row = tIndex; + this._col = pIndex; - if (this._capture(this._scanId, tIndex, pIndex)) + // First capture the IR Image. + var payload = pantilt._doIRCapture(); + + if (payload == null) { - this._captureRetries = 0; - this._captureFinished(); + pantilt._abortScan(); + return; } - else + else if (!pantilt._config.rgbEnabled) { - this._captureRetries += 1; - setTimeout( this._tryCapture.bind(this), this._config.recaptureTime ); + pantilt._logger.info('Emitting sensor data for node'); + pantilt.emit('data', payload); + pantilt._captureFinished(); + return; } + + var rgbPromise = pantilt._doRGBCapture(); + + rgbPromise.then( + function success(data) { + pantilt._logger.info('Emitting sensor data for node'); + payload.data.camera.rgbimage = data + pantilt.emit('data', payload); + pantilt._captureFinished(); + }, + function fail() { + pantilt._logger.info('Fail to capture RGB image'); + pantilt._abortScan(); + } + ); }; - + /** * @class LeptonPanTiltCameraConnector * @method _captureFinished @@ -397,32 +435,6 @@ LeptonPanTiltCameraConnector.prototype._captureFinished = function() { } } -LeptonPanTiltCameraConnector.prototype._captureFinishedOLD = function() { - this._logger.info('_captureFinished'); - - this._panServo.index += this._panServo.indexIncrement; - - var tIndex = this._tiltServo.index; - var pIndex = this._panServo.index; - var nPans = this._config.moves[ tIndex ].pans.length; - - if (pIndex < 0 || pIndex == nPans) - { - var index = tIndex + this._tiltServo.indexIncrement; - if (index < 0 || index == this._config.moves.length) - { - this._tiltServo.indexIncrement *= -1; - - // we're done with scanning. - this._scanFinished(); - } - } - else - { - this._pan(); - } -}; - /** * @class LeptonPanTiltCameraConnector * @method _startScan @@ -434,7 +446,7 @@ LeptonPanTiltCameraConnector.prototype._startScan = function() { this._logger.info('_startScan'); this._scanning = true; - this._scanId = _shortId.generate(); + this._scan = _shortId.generate(); this._logger.info('tilt: ' + this._tiltServo.index + '/' + this._tiltServo.angle); this._logger.info('pan: ' + this._panServo.index + '/' + this._panServo.angle); @@ -468,6 +480,8 @@ LeptonPanTiltCameraConnector.prototype._abortScan = function() { this._tiltServo.reset(); this._panServo.reset(); this._scanning = false; + this._row = 0; + this._col = 0; } /** @@ -476,7 +490,7 @@ LeptonPanTiltCameraConnector.prototype._abortScan = function() { * @protected */ LeptonPanTiltCameraConnector.prototype._scanFinished = function() { - this._logger.info('_scanFinished scanid: ' + this._scanId); + this._logger.info('_scanFinished scanid: ' + this._scan); this._scanning = false; } @@ -487,7 +501,6 @@ LeptonPanTiltCameraConnector.prototype._scanFinished = function() { * @protected */ LeptonPanTiltCameraConnector.prototype._updateScan = function() { - //this._logger.info('_updateScan'); this._updateCount += 1; @@ -519,14 +532,45 @@ LeptonPanTiltCameraConnector.prototype._process = function() { this._startScan(); } + /** + * @class LeptonPanTiltCameraConnector + * @method __doIRCapture + * @protected + */ +LeptonPanTiltCameraConnector.prototype._doIRCapture = function() { + + this._logger.info("_doIRCapture"); + + var data = null; + while(true) + { + data = this._captureIR(); + if (data != null) { + break; + } + + if (this._captureRetries == this._config.recaptureMaxRetries) { + this._logger.info('Maximum recapture retries reached - aborting scan.'); + break; + } + + console.log('trying ...', this._captureRetries); + setTimeout(this._doIRCapture.bind(this), this._config.recaptureTime); + this._captureRetries += 1; + } + + return data; +}; + + /** * @class LeptonPanTiltCameraConnector * @method _process * @protected */ -LeptonPanTiltCameraConnector.prototype._capture = function(scanId, row, col) { +LeptonPanTiltCameraConnector.prototype._captureIR = function() { - this._logger.info('_capture scan: '+ scanId + ' row: '+ row + ' col: ' + col); + this._logger.info('_captureIR scan: '+ this._scan + ' row: '+ this._row + ' col: ' + this._col); if(this._camera) { @@ -542,8 +586,9 @@ LeptonPanTiltCameraConnector.prototype._capture = function(scanId, row, col) { rows: 0, cols: 0, delta: 0, - scanId: scanId, - position: row + ',' + col + scan: this._scan, + row: this._row, + col: this._col }; do { var txBuf = new Buffer(PACKET_SIZE); @@ -604,15 +649,16 @@ LeptonPanTiltCameraConnector.prototype._capture = function(scanId, row, col) { timestamp: Date.now(), camera: { metadata: metadata, - lines: packets + lines: packets, + rgbdata: null } } }; - this._logger.info('Emitting sensor data for node'); - this.emit('data', payload); + //this._logger.info('Emitting sensor data for node'); + //this.emit('data', payload); - return true; + return payload; } else { this._logger.warn('Error reading frame from camera. No data to send'); @@ -622,7 +668,82 @@ LeptonPanTiltCameraConnector.prototype._capture = function(scanId, row, col) { this._logger.warn('Camera not initialized and ready'); } - return false; + return null; +}; + + +LeptonPanTiltCameraConnector.prototype._doRGBCapture = function() { + + this._logger.info("_doRGBCapture()"); + + var deferred = _q.defer(); + var pantilt = this; + + this._setEnableLights( true ); + + var path = RGB_PATH_PREFIX + pantilt._row + '_' + pantilt._col + '.jpg'; + path = _path.join(__dirname, path); + + var options = [ '--device', this._config.rgbDevice, + '--resolution', this._config.rgbResolution, + path]; + var fswebcam = spawn( this._config.rgbBinary, options ); + + // Log any errors. + fswebcam.on('error', function (data) { + pantilt._logger.warn(data); + pantilt._setEnableLights( false ); + return deferred.reject(null); + }); + + // continue processing after it has taken photos + fswebcam.on('exit', function (code) { + pantilt._setEnableLights( false ); + pantilt.rgbRetries = RGB_MAX_READ_RETRIES; + var data = pantilt._readRGBImage(); + return deferred.resolve(data); + }); + + return deferred.promise +}; + +LeptonPanTiltCameraConnector.prototype._readRGBImage = function( ) { + + this._logger.info('_readRGBImage()'); + + var path = RGB_PATH_PREFIX + this._row + '_' + this._col + '.jpg'; + path = _path.join(__dirname, path); + + var data = null; + if (this.rgbRetries == 0) return null; + + try + { + // read binary data + var bitmap = _fs.readFileSync( path ); + + // convert binary data to base64 encoded string + data = new Buffer(bitmap).toString('base64'); + } + catch(e) + { + console.log('retrying....error: ' + e ); + this.rgbRetries -= 1; + setTimeout(this._readRGBImage.bind(this), 100); + } + + return data; +}; + +LeptonPanTiltCameraConnector.prototype._setEnableLights = function( on ) { + if (on) + { + _wiringPi.digitalWrite(this._config.cameraLightsPin, _wiringPi.HIGH); + } + else + { + _wiringPi.digitalWrite(this._config.cameraLightsPin, _wiringPi.LOW); + } }; module.exports = LeptonPanTiltCameraConnector; diff --git a/config/config.json b/config/config.json old mode 100755 new mode 100644 index f086535..0220dfd --- a/config/config.json +++ b/config/config.json @@ -11,25 +11,55 @@ "LeptonPanTiltCamera": "./lepton-pan-tilt-connector" }, "cloudConnectors": { - "testgw-cnc-cloud": { - "type": "CncCloud", - "config": { - "host": "api-iot-dev.analoggarage.com", - "port": 8443, - "protocol": "mqtts", - "networkInterface": "en0", - "gatewayname": "testgw", - "account": "test-account", - "password": "", - "topics": "" - } - } }, "deviceConnectors": { "testgw-cnc-gateway": { "type": "CncGateway", "config": {} - } + }, + "lepton-pan-tilt-camera0": { + "type": "LeptonPanTiltCamera", + "config": { + "scanUpdateFrequency":50, + "recaptureMaxRetries":2, + "recaptureTime":500, + "pollFrequency": 6000, + "spiDevice": "/dev/spidev0.1", + "i2cDevice": "/dev/i2c-1", + "maxRetries": 600, + "cameraResetPin": 23, + "cameraLightsPin": 4, + "rgbEnabled": false, + "rgbDevice": "/dev/video0", + "rgbBinary": "/usr/bin/fswebcam", + "rgbResolution": "640x480", + + "tiltServo": { + "pin": 24, + "minAngle": 30, + "maxAngle": 110, + "incAngle": 1 + }, + + "panServo": { + "pin": 1, + "minAngle": 40, + "maxAngle": 120, + "incAngle": 1 + }, + + "moves": [ { + "tilt": 50, + "pans": [60,70,80,90,100] + }, { + "tilt": 65, + "pans": [60,70,80,90,100] + }, { + "tilt": 80, + "pans": [60,70,80,90,100] + } ] + } + } }, "cloudConnectors.ignore": { "testgw-cnc-cloud": { diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000..e21f397 --- /dev/null +++ b/data/README.md @@ -0,0 +1 @@ +# directory for temporary rgb images From 6c897b637ee95cae822ba170f7dda12a40b0c102 Mon Sep 17 00:00:00 2001 From: fegonda Date: Tue, 2 Aug 2016 13:11:01 -0400 Subject: [PATCH 2/4] enabling raspistill functionality to capture RGB images --- app/connectors/lepton-pan-tilt-connector.js | 122 ++++----- app/utils/node-picam/.gitignore | 14 ++ app/utils/node-picam/README.md | 4 + app/utils/node-picam/lib/Camera.js | 84 +++++++ app/utils/node-picam/lib/OCVCamera.js | 19 ++ app/utils/node-picam/lib/StillOptions.js | 264 ++++++++++++++++++++ app/utils/node-picam/lib/index.js | 9 + app/utils/node-picam/package.json | 21 ++ config/config.json | 12 +- package.json | 1 + 10 files changed, 466 insertions(+), 84 deletions(-) create mode 100644 app/utils/node-picam/.gitignore create mode 100644 app/utils/node-picam/README.md create mode 100644 app/utils/node-picam/lib/Camera.js create mode 100644 app/utils/node-picam/lib/OCVCamera.js create mode 100644 app/utils/node-picam/lib/StillOptions.js create mode 100644 app/utils/node-picam/lib/index.js create mode 100644 app/utils/node-picam/package.json diff --git a/app/connectors/lepton-pan-tilt-connector.js b/app/connectors/lepton-pan-tilt-connector.js index 40aaf39..b18b779 100755 --- a/app/connectors/lepton-pan-tilt-connector.js +++ b/app/connectors/lepton-pan-tilt-connector.js @@ -10,6 +10,9 @@ var _wiringPi = require('../utils/wiring-pi-wrapper'); var PollingConnector = require('iot-client-lib').PollingConnector; var spawn = require('child_process').spawn; +var PiCamera = require('../utils/node-picam/lib/Camera').Camera; +//var PiCameraOptions = require('../utils/node-picam/lib/StillOptions').Options; + var PACKET_SIZE = 164; var PACKETS_PER_FRAME = 60; var DEFAULT_MAX_RETRIES = 750; @@ -41,7 +44,6 @@ Servo.prototype.move = function(angle, callback) { this.target = angle; this.callback = callback; - if (this.target < this.angle) { this.direction = -1; @@ -53,7 +55,6 @@ Servo.prototype.move = function(angle, callback) { if (this.angle == this.target && this.callback != null) { this.callback(); - //this.callback = null; } }; @@ -79,7 +80,6 @@ Servo.prototype.update = function() { if (this.callback != null) { this.callback(); - //this.callback = null; } } }; @@ -106,6 +106,7 @@ function LeptonPanTiltCameraConnector(id) { this._panServo = null; this._updateCount = 0; + this._picam = null; } _util.inherits(LeptonPanTiltCameraConnector, PollingConnector); @@ -127,7 +128,7 @@ LeptonPanTiltCameraConnector.prototype._resetCamera = function() { this._logger.info('Camera reset complete'); _wiringPi.digitalWrite(this._config.cameraResetPin, 1); //this._camera = camera; - }.bind(this), 500); + }.bind(this), this._config.cameraResetTimeout); }; /** @@ -205,13 +206,6 @@ LeptonPanTiltCameraConnector.prototype._start = function() { this._tiltServo = new Servo( this._config.tiltServo ); this._panServo = new Servo( this._config.panServo ); - console.log('tiltservo:'); - console.log( this._tiltServo ); - - console.log('panservo:'); - console.log( this._panServo ); - - /* setup wiring pi */ //_wiringPi.setup('wpi'); _wiringPi.pinMode( this._config.tiltServo.pin, _wiringPi.PWM_OUTPUT ); @@ -372,29 +366,29 @@ LeptonPanTiltCameraConnector.prototype._capture = function() { if (payload == null) { - pantilt._abortScan(); - return; + pantilt._abortScan(); + return; } else if (!pantilt._config.rgbEnabled) { pantilt._logger.info('Emitting sensor data for node'); - pantilt.emit('data', payload); - pantilt._captureFinished(); - return; + pantilt.emit('data', payload); + pantilt._captureFinished(); + return; } var rgbPromise = pantilt._doRGBCapture(); rgbPromise.then( - function success(data) { - pantilt._logger.info('Emitting sensor data for node'); - payload.data.camera.rgbimage = data - pantilt.emit('data', payload); - pantilt._captureFinished(); + function success(image) { + pantilt._logger.info('Emitting sensor data for node'); + payload.data.camera.rgbimage = image; + pantilt.emit('data', payload); + pantilt._captureFinished(); }, - function fail() { - pantilt._logger.info('Fail to capture RGB image'); - pantilt._abortScan(); + function fail(error) { + pantilt._logger.info('Fail to capture RGB image. Error: ' + error); + pantilt._abortScan(); } ); }; @@ -546,12 +540,12 @@ LeptonPanTiltCameraConnector.prototype._doIRCapture = function() { { data = this._captureIR(); if (data != null) { - break; + break; } if (this._captureRetries == this._config.recaptureMaxRetries) { - this._logger.info('Maximum recapture retries reached - aborting scan.'); - break; + this._logger.info('Maximum recapture retries reached - aborting scan.'); + break; } console.log('trying ...', this._captureRetries); @@ -658,7 +652,7 @@ LeptonPanTiltCameraConnector.prototype._captureIR = function() { //this._logger.info('Emitting sensor data for node'); //this.emit('data', payload); - return payload; + return payload; } else { this._logger.warn('Error reading frame from camera. No data to send'); @@ -676,63 +670,35 @@ LeptonPanTiltCameraConnector.prototype._doRGBCapture = function() { this._logger.info("_doRGBCapture()"); - var deferred = _q.defer(); - var pantilt = this; + var deferred = _q.defer(); + var pantilt = this; this._setEnableLights( true ); - var path = RGB_PATH_PREFIX + pantilt._row + '_' + pantilt._col + '.jpg'; - path = _path.join(__dirname, path); - - var options = [ '--device', this._config.rgbDevice, - '--resolution', this._config.rgbResolution, - path]; - var fswebcam = spawn( this._config.rgbBinary, options ); - - // Log any errors. - fswebcam.on('error', function (data) { - pantilt._logger.warn(data); - pantilt._setEnableLights( false ); - return deferred.reject(null); - }); - - // continue processing after it has taken photos - fswebcam.on('exit', function (code) { - pantilt._setEnableLights( false ); - pantilt.rgbRetries = RGB_MAX_READ_RETRIES; - var data = pantilt._readRGBImage(); - return deferred.resolve(data); - }); + var options = PiCamera.DEFAULT_PROFILE.opts; + options.controls.flipVertical = false; + options.settings.width = this._config.rgbWidth;; + options.settings.height = this._config.rgbHeight;; + this._picam = new PiCamera( options ); - return deferred.promise -}; + // take the picture + pantilt._picam.takeJPG(); -LeptonPanTiltCameraConnector.prototype._readRGBImage = function( ) { - - this._logger.info('_readRGBImage()'); - - var path = RGB_PATH_PREFIX + this._row + '_' + this._col + '.jpg'; - path = _path.join(__dirname, path); - - var data = null; - if (this.rgbRetries == 0) return null; - - try - { - // read binary data - var bitmap = _fs.readFileSync( path ); + // handle snapped event (i.e when the picture data is fully captured) + pantilt._picam.on('snapped', function(results) + { + pantilt._setEnableLights( false ); + return deferred.resolve(results.image); + }); - // convert binary data to base64 encoded string - data = new Buffer(bitmap).toString('base64'); - } - catch(e) - { - console.log('retrying....error: ' + e ); - this.rgbRetries -= 1; - setTimeout(this._readRGBImage.bind(this), 100); - } + // handle errors + pantilt._picam.on('error', function(results) + { + pantilt._setEnableLights( false ); + return deferred.reject(results.error); + }); - return data; + return deferred.promise }; LeptonPanTiltCameraConnector.prototype._setEnableLights = function( on ) { diff --git a/app/utils/node-picam/.gitignore b/app/utils/node-picam/.gitignore new file mode 100644 index 0000000..f356293 --- /dev/null +++ b/app/utils/node-picam/.gitignore @@ -0,0 +1,14 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log diff --git a/app/utils/node-picam/README.md b/app/utils/node-picam/README.md new file mode 100644 index 0000000..9a42014 --- /dev/null +++ b/app/utils/node-picam/README.md @@ -0,0 +1,4 @@ +node-picam +========== + +wrapper for raspberrypi camera module diff --git a/app/utils/node-picam/lib/Camera.js b/app/utils/node-picam/lib/Camera.js new file mode 100644 index 0000000..936ac8b --- /dev/null +++ b/app/utils/node-picam/lib/Camera.js @@ -0,0 +1,84 @@ +var Options = require('./StillOptions').Options; +var OptionsConstants = require('./StillOptions').Constants; +var util = require("util"); +var EventEmitter = require("events").EventEmitter; +var spawn = require("child_process").spawn; + +var COMMAND = 'raspistill'; + +module.exports.Camera = Camera; + +Camera.Options = OptionsConstants; + +Camera.DEFAULT_PROFILE = new Options({ + controls: { + exposure: OptionsConstants.CONTROLS.EXPOSURE.AUTO, + awb: OptionsConstants.CONTROLS.AWB.AUTO, + flipVertical: true + }, + settings: { + imageType: OptionsConstants.SETTINGS.ENCODING.JPG, + quality: 75 + } +}); + +function Camera(opts) { + this.opts = new Options(opts); + EventEmitter.call(this); +} + +util.inherits(Camera, EventEmitter); + +var generateTakePhotoFunction = function(format) { + var a = function (optionalIdentifier, optionalOpts) { + var opts = optionalOpts || this.opts; + var id = optionalIdentifier || Date.now() + var options = buildOptions(opts, OptionsConstants.SETTINGS.ENCODING[format]); + takePhoto(id, options, this); + return id; + }; + return a; +}; + +Camera.prototype.takeJPG = generateTakePhotoFunction('JPG'); +Camera.prototype.takePNG = generateTakePhotoFunction('PNG'); +Camera.prototype.takeBMP = generateTakePhotoFunction('BMP'); + +Camera.prototype.setFilter = function (filter) { + this.opts.merge({controls: {filter: filter}}); +}; + +function buildOptions(options, imageType) { + var cloned = options.clone(); + cloned.merge({settings: {imageType: imageType}}); + return cloned; +} + +function takePhoto(id, options, camera) { + var pid = spawn(COMMAND, ["--output", "-"].concat(options.toArray())); + + var imageData = new Buffer(0); + + pid.stdout.on('data', function (data) { + imageData = Buffer.concat([imageData, data]); + }); + + pid.on('error', function (err) { + camera.emit('error', {id: id, error: err}); + }); + + pid.on('close', function (code) { + camera.buildImage(imageData, function (img) { + camera.emit('snapped', {id: id, image: img}); + }); + }); +} + + +/** + * allow subclass to construct different kind of image. + * @param data + */ +Camera.prototype.buildImage = function (data, callback) { + callback.call(null, data); +}; diff --git a/app/utils/node-picam/lib/OCVCamera.js b/app/utils/node-picam/lib/OCVCamera.js new file mode 100644 index 0000000..0ba52a5 --- /dev/null +++ b/app/utils/node-picam/lib/OCVCamera.js @@ -0,0 +1,19 @@ +var Camera = require("./Camera").Camera; +var util = require("util"); +var cv = require('opencv'); + +function OCVCamera(opts) { + Camera.call(this, opts); +} + +util.inherits(OCVCamera, Camera); + + +OCVCamera.prototype.buildImage = function (data, callback) { + cv.readImage(data, function (err, img) { + callback.call(null, img); + }); +}; + +module.exports.OCVCamera = OCVCamera; +module.exports.OCVCamera.Options = Camera.Options; diff --git a/app/utils/node-picam/lib/StillOptions.js b/app/utils/node-picam/lib/StillOptions.js new file mode 100644 index 0000000..f99ddf6 --- /dev/null +++ b/app/utils/node-picam/lib/StillOptions.js @@ -0,0 +1,264 @@ +var lodash = require('lodash'); + +var CONSTANTS = { + PREVIEW: { + NONE: 'none', + FULLSCREEN: 'fullscreen' + }, + CONTROLS: { + EXPOSURE: { + OFF: 'off', + AUTO: 'auto', + NIGHT: 'night', + NIGHTPREVIEW: 'nightpreview', + BACKLIGHT: 'backlight', + SPOTLIGHT: 'spotlight', + SPORTS: 'sports', + SNOW: 'snow', + BEACH: 'beach', + VERYLONG: 'verylong', + FIXEDFPS: 'fixedfps', + ANTISHAKE: 'antishake', + FIREWORKS: 'fireworks' + }, + AWB: { + OFF: 'off', + AUTO: 'auto', + SUN: 'sun', + CLOUD: 'cloud', + SHADE: 'shade', + TUNGSTEN: 'tungsten', + FLORESCENT: 'florescent', + INCANDESCENT: 'incandescent', + FLASH: 'flash', + HORIZON: 'horizon' + }, + FILTER: { + NONE: 'none', + NEGATIVE: 'negative', + SOLARISE: 'solarise', + POSTERIZE: 'posterize', + WHITEBOARD: 'whiteboard', + BLACKBOARD: 'blackboard', + SKETCH: 'sketch', + DENOISE: 'denoise', + EMBOSS: 'emboss', + OILPANT: 'oilpaint', + HATCH: 'hatch', + OPEN: 'gpen', + PASTEL: 'pastel', + WATERCOLOR: 'watercolour', + FILM: 'film', + BLUR: 'blur', + SATURATION: 'saturation', + COLORSWAP: 'colourswap', + WASHEDOUT: 'washedout', + POSTERISE: 'posterise', + COLOURPOINT: 'colourpoint', + COLOURBALANCE: 'colourbalance', + CARTOON: 'cartoon' + }, + METERING: { + AVERAGE: 'average', + SPOT: 'spot', + BACKLIT: 'backlit', + MATRIX: 'matrix' + } + }, + SETTINGS: { + ENCODING: { + JPG: 'jpg', + BMP: 'bmp', + GIF: 'gif', + PNG: 'png' + } + } +} + +module.exports.Constants = CONSTANTS; +module.exports.Options = Options; + +function Options(opts) { + if (opts instanceof Options) { + this.opts = opts.opts; + } else { + this.opts = opts; + } +} + +/** + * modify internal data + * @param json + */ +Options.prototype.merge = function (json) { + lodash.merge(this.opts, json); +}; + +/** + * clone + * @param json + */ +Options.prototype.clone = function () { + return new Options(lodash.clone(this.opts,true)); +}; + + +Options.prototype.toArray = function () { + var opts = this.opts; + var result = []; + + if (isDefined(opts.preview) && opts.preview.length == 1) { + var val = opts.preview[0]; + var previewFlag = options.PREVIEW[val]; + if (val == "none") { + result.push("--nopreview"); + } else if (val == "fullscreen") { + result.push("--fullscreen"); + } else if (!previewFlag) { + result.push(val); + //TODO: [medium] (nhat) - validate the x,y,w,h string + } + } + + if (!isDefined(opts.controls)) { + return result; + } + + if (isDefined(opts.controls.sharpness)) { + result.push("--sharpness"); + result.push(opts.controls.sharpness); + } + + if (isDefined(opts.controls.contrast)) { + result.push("--contrast"); + result.push(opts.controls.contrast); + } + + if (isDefined(opts.controls.brightness)) { + result.push("--brightness"); + result.push(opts.controls.brightness); + } + + if (isDefined(opts.controls.saturation)) { + result.push("--saturation"); + result.push(opts.controls.saturation); + } + + if (isDefined(opts.controls.saturation)) { + result.push("--saturation"); + result.push(opts.controls.saturation); + } + + if (isDefined(opts.controls.iso) && !!opts.controls.iso) { + result.push("--ISO"); + } + + if (isDefined(opts.controls.vstab) && !!opts.controls.vstab) { + result.push("--vstab"); + } + + if (isDefined(opts.controls.ev) && !!opts.controls.ev) { + result.push("--ev"); + } + + if (isDefined(opts.controls.exposure)) { + result.push("--exposure"); + result.push(opts.controls.exposure); + } + + if (isDefined(opts.controls.awb)) { + result.push("--awb"); + result.push(opts.controls.awb); + } + + if (isDefined(opts.controls.filter)) { + result.push("--imxfx"); + result.push(opts.controls.filter); + } + + if (isDefined(opts.controls.colorControl)) { + result.push("--colfx"); + result.push(opts.controls.colorControl); + //TODO: [medium] (nhat) - check for format U:V (0-255) + } + + if (isDefined(opts.controls.flipVertical) && !!opts.controls.flipVertical) { + result.push("--vflip"); + } + + if (isDefined(opts.controls.flipHorizontal) && !!opts.controls.flipHorizontal) { + result.push("--hflip"); + } + + if (isDefined(opts.controls.rotation)) { + result.push("--rotation"); + result.push(opts.controls.rotation); + } + + if (isDefined(opts.controls.metering)) { + result.push("--metering"); + result.push(opts.controls.metering); + } + + if (!isDefined(opts.settings)) { + return result; + } + + if (isDefined(opts.settings.quality)) { + result.push("--quality"); + result.push(opts.settings.quality); + } + + if (isDefined(opts.settings.width)) { + result.push("--width"); + result.push(opts.settings.width); + } + + if (isDefined(opts.settings.height)) { + result.push("--height"); + result.push(opts.settings.height); + } + + if (isDefined(opts.settings.raw) && !!opts.settings.raw) { + result.push("--raw"); + } + + if (isDefined(opts.settings.timeout)) { + result.push("--timeout"); + result.push(opts.settings.timeout); + } + + if (isDefined(opts.settings.imageType)) { + result.push("--encoding"); + result.push(opts.settings.imageType); + } + + //TODO: [medium] (nhat) - add timelapse + + + return result; +}; + +Options.prototype.toString = function () { + return this.toArray().join(" "); +} + + +function isDefined(val) { + return typeof(val) != "undefined"; +} + + +/* + + */ +function assign(obj, keyPath, value) { + var lastKeyIndex = keyPath.length - 1; + for (var i = 0; i < lastKeyIndex; ++i) { + var key = keyPath[i]; + if (!(key in obj)) + obj[key] = {} + obj = obj[key]; + } + obj[keyPath[lastKeyIndex]] = value; +} diff --git a/app/utils/node-picam/lib/index.js b/app/utils/node-picam/lib/index.js new file mode 100644 index 0000000..45506ed --- /dev/null +++ b/app/utils/node-picam/lib/index.js @@ -0,0 +1,9 @@ +var Camera = require('./Camera').Camera; +var Options = require('./StillOptions').Options; +var OCVCamera = require('./OCVCamera').OCVCamera; + +module.exports = { + Camera: Camera, + Options: Options, + OCVCamera: OCVCamera +}; diff --git a/app/utils/node-picam/package.json b/app/utils/node-picam/package.json new file mode 100644 index 0000000..87f7cf8 --- /dev/null +++ b/app/utils/node-picam/package.json @@ -0,0 +1,21 @@ +{ + "name": "picam", + "version": "0.1.3", + "author": { + "name": "hachr", + "email": "hach@hachr.com", + "url": "https://github.com/hachr" + }, + "description": "Simple wrapper for raspistill", + "keywords": ["raspistill", "camera", "raspberry", "pi"], + "main": "lib", + "repository": "git://github.com/hachr/node-picam.git", + "devDependencies": { + "lodash": "*" + }, + "licenses": [ + { + "type": "MIT" + } + ] +} diff --git a/config/config.json b/config/config.json index 0220dfd..1eb7f7e 100644 --- a/config/config.json +++ b/config/config.json @@ -26,13 +26,13 @@ "pollFrequency": 6000, "spiDevice": "/dev/spidev0.1", "i2cDevice": "/dev/i2c-1", - "maxRetries": 600, + "maxRetries": 200, + "cameraResetTimeout":1000, "cameraResetPin": 23, - "cameraLightsPin": 4, - "rgbEnabled": false, - "rgbDevice": "/dev/video0", - "rgbBinary": "/usr/bin/fswebcam", - "rgbResolution": "640x480", + "cameraLightsPin": 4, + "rgbEnabled": true, + "rgbWidth": 640, + "rgbHeight": 480, "tiltServo": { "pin": 24, diff --git a/package.json b/package.json index e2a3139..b66757e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "clone": "^1.0.2", "iot-client-lib": "^3.1.1", + "lodash": "^4.14.1", "macaddress": "^0.2.8", "mqtt": "^1.6.1", "node-clone": "^0.1.1", From e044616ccde95ecf25741dc2165aea7d3e5ba8b3 Mon Sep 17 00:00:00 2001 From: fegonda Date: Wed, 3 Aug 2016 12:46:34 -0400 Subject: [PATCH 3/4] updates to emit base64 rgb image data and option to save to file --- app/connectors/lepton-pan-tilt-connector.js | 78 ++++++++++++++++----- app/utils/node-picam/lib/Camera.js | 4 +- app/utils/node-picam/lib/StillOptions.js | 8 +++ config/config.json | 7 +- 4 files changed, 74 insertions(+), 23 deletions(-) diff --git a/app/connectors/lepton-pan-tilt-connector.js b/app/connectors/lepton-pan-tilt-connector.js index b18b779..731def3 100755 --- a/app/connectors/lepton-pan-tilt-connector.js +++ b/app/connectors/lepton-pan-tilt-connector.js @@ -11,7 +11,6 @@ var PollingConnector = require('iot-client-lib').PollingConnector; var spawn = require('child_process').spawn; var PiCamera = require('../utils/node-picam/lib/Camera').Camera; -//var PiCameraOptions = require('../utils/node-picam/lib/StillOptions').Options; var PACKET_SIZE = 164; var PACKETS_PER_FRAME = 60; @@ -201,19 +200,21 @@ LeptonPanTiltCameraConnector.prototype._start = function() { this._logger.info( this._config.tiltServo ); - /* setup servos */ - console.log('----iinitializing servos...'); - this._tiltServo = new Servo( this._config.tiltServo ); - this._panServo = new Servo( this._config.panServo ); + /* setup servos */ + console.log('----iinitializing servos...'); + this._tiltServo = new Servo( this._config.tiltServo ); + this._panServo = new Servo( this._config.panServo ); - /* setup wiring pi */ - //_wiringPi.setup('wpi'); - _wiringPi.pinMode( this._config.tiltServo.pin, _wiringPi.PWM_OUTPUT ); - _wiringPi.pinMode( this._config.panServo.pin, _wiringPi.PWM_OUTPUT ); - _wiringPi.pinMode( this._config.cameraLightsPin, _wiringPi.OUTPUT ); - _wiringPi.pwmSetMode( _wiringPi.PWM_MODE_MS ); - _wiringPi.pwmSetClock( 375 ); - _wiringPi.pwmSetRange( 1024 ); + /* setup wiring pi */ + //_wiringPi.setup('wpi'); + _wiringPi.pinMode( this._config.tiltServo.pin, _wiringPi.PWM_OUTPUT ); + _wiringPi.pinMode( this._config.panServo.pin, _wiringPi.PWM_OUTPUT ); + _wiringPi.pinMode( this._config.cameraLightsPin, _wiringPi.OUTPUT ); + _wiringPi.pwmSetMode( _wiringPi.PWM_MODE_MS ); + _wiringPi.pwmSetClock( 375 ); + _wiringPi.pwmSetRange( 1024 ); + _wiringPi.pwmWrite( this._config.tiltServo.pin, this._config.tiltServo.minAngle ); + _wiringPi.pwmWrite( this._config.panServo.pin, this._config.panServo.minAngle ); this._logger.info('Initializing camera reset pin: [%s]', this._config.cameraResetPin); @@ -336,13 +337,12 @@ LeptonPanTiltCameraConnector.prototype._pan = function() { LeptonPanTiltCameraConnector.prototype._panFinished = function() { this._logger.info('_panFinished - ' + this._panServo.index + '/' + this._panServo.angle); - //this._tryCapture(); this._capture(); } /** * @class LeptonPanTiltCameraConnector - * @method _tryCapture + * @method _capture * @protected */ LeptonPanTiltCameraConnector.prototype._capture = function() { @@ -351,6 +351,8 @@ LeptonPanTiltCameraConnector.prototype._capture = function() { if (this._camera == null) return; + console.log('capturing....'); + var tIndex = this._tiltServo.index; var pIndex = this._panServo.index; var tAngle = this._tiltServo.angle; @@ -369,7 +371,12 @@ LeptonPanTiltCameraConnector.prototype._capture = function() { pantilt._abortScan(); return; } - else if (!pantilt._config.rgbEnabled) + + if (pantilt._config.saveToFile) { + pantilt._irPacketsToFile( payload.data.camera.lines ); + } + + if (!pantilt._config.rgbEnabled) { pantilt._logger.info('Emitting sensor data for node'); pantilt.emit('data', payload); @@ -382,8 +389,11 @@ LeptonPanTiltCameraConnector.prototype._capture = function() { rgbPromise.then( function success(image) { pantilt._logger.info('Emitting sensor data for node'); - payload.data.camera.rgbimage = image; + + // encode this image with 64 hex + payload.data.camera.rgbimage = image.toString('base64'); pantilt.emit('data', payload); + pantilt._captureFinished(); }, function fail(error) { @@ -503,8 +513,6 @@ LeptonPanTiltCameraConnector.prototype._updateScan = function() { this._tiltServo.update(); this._panServo.update(); } - - //this._scanUpdateTimer = setTimeout( this._updateScan.bind(this), this._config.scanUpdateFrequency ); } /** @@ -679,6 +687,17 @@ LeptonPanTiltCameraConnector.prototype._doRGBCapture = function() { options.controls.flipVertical = false; options.settings.width = this._config.rgbWidth;; options.settings.height = this._config.rgbHeight;; + options.settings.timeout = 1; + + if (pantilt._config.saveToFile) { + var tIndex = pantilt._tiltServo.index; + var pIndex = pantilt._panServo.index; + var tAngle = pantilt._config.moves[ tIndex ].tilt; + var pAngle = pantilt._config.moves[ tIndex ].pans[ pIndex ]; + + options.settings.outputPath = "./data/rgb_image_" + tAngle + "_" + pAngle + ".jpg"; + } + this._picam = new PiCamera( options ); // take the picture @@ -712,4 +731,25 @@ LeptonPanTiltCameraConnector.prototype._setEnableLights = function( on ) { } }; +LeptonPanTiltCameraConnector.prototype._irPacketsToFile = function( packets ) { + + var tIndex = this._tiltServo.index; + var pIndex = this._panServo.index; + var tAngle = this._config.moves[ tIndex ].tilt; + var pAngle = this._config.moves[ tIndex ].pans[ pIndex ]; + var path = "./data/ir_image_" + tAngle + "_" + pAngle + ".dat"; + + console.log('captured: ' + path ); + _fs.writeFile(path, JSON.stringify(packets), + function(err) + { + if(err) + { + console.log( err ); + } + } + ); +} // packetsToFile + + module.exports = LeptonPanTiltCameraConnector; diff --git a/app/utils/node-picam/lib/Camera.js b/app/utils/node-picam/lib/Camera.js index 936ac8b..46a0b84 100644 --- a/app/utils/node-picam/lib/Camera.js +++ b/app/utils/node-picam/lib/Camera.js @@ -55,7 +55,9 @@ function buildOptions(options, imageType) { } function takePhoto(id, options, camera) { - var pid = spawn(COMMAND, ["--output", "-"].concat(options.toArray())); + + //var pid = spawn(COMMAND, ["--output", "-"].concat(options.toArray())); + var pid = spawn(COMMAND, options.toArray()); var imageData = new Buffer(0); diff --git a/app/utils/node-picam/lib/StillOptions.js b/app/utils/node-picam/lib/StillOptions.js index f99ddf6..6875d73 100644 --- a/app/utils/node-picam/lib/StillOptions.js +++ b/app/utils/node-picam/lib/StillOptions.js @@ -233,6 +233,14 @@ Options.prototype.toArray = function () { result.push(opts.settings.imageType); } + result.push("--output"); + if (isDefined(opts.settings.outputPath)) { + result.push(opts.settings.outputPath); + } + else { + result.push("-"); + } + //TODO: [medium] (nhat) - add timelapse diff --git a/config/config.json b/config/config.json index 1eb7f7e..de1c7af 100644 --- a/config/config.json +++ b/config/config.json @@ -20,9 +20,10 @@ "lepton-pan-tilt-camera0": { "type": "LeptonPanTiltCamera", "config": { + "saveToFile":false, "scanUpdateFrequency":50, "recaptureMaxRetries":2, - "recaptureTime":500, + "recaptureTime":1000, "pollFrequency": 6000, "spiDevice": "/dev/spidev0.1", "i2cDevice": "/dev/i2c-1", @@ -49,13 +50,13 @@ }, "moves": [ { - "tilt": 50, + "tilt": 80, "pans": [60,70,80,90,100] }, { "tilt": 65, "pans": [60,70,80,90,100] }, { - "tilt": 80, + "tilt": 50, "pans": [60,70,80,90,100] } ] } From b140e86e1b8c40af787b62fd910804643265fe30 Mon Sep 17 00:00:00 2001 From: fegonda Date: Wed, 10 Aug 2016 10:35:17 -0400 Subject: [PATCH 4/4] fix ir camera reset problem caused by setTimeout --- .gitignore | 4 + app/connectors/lepton-pan-tilt-connector.js | 270 ++++++++------------ app/utils/node-picam/lib/StillOptions.js | 6 +- config/config.json | 22 +- 4 files changed, 120 insertions(+), 182 deletions(-) diff --git a/.gitignore b/.gitignore index 86da7a9..a2e459f 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +#temp image files +data/*.jpg* +data/*.dat* + # vi swap files *.swp *.swo diff --git a/app/connectors/lepton-pan-tilt-connector.js b/app/connectors/lepton-pan-tilt-connector.js index 731def3..eaba2ff 100755 --- a/app/connectors/lepton-pan-tilt-connector.js +++ b/app/connectors/lepton-pan-tilt-connector.js @@ -29,7 +29,7 @@ function Servo( config ) { }; Servo.prototype.reset = function() { - this.angle = 0; + this.angle = this.config.minAngle; this.target = 0; this.direction = 1; this.callback = null; @@ -71,7 +71,6 @@ Servo.prototype.update = function() { /* write data to the signal pin */ _wiringPi.pwmWrite( this.config.pin, this.angle ); - //console.log('writing angle: ' + this.angle + ' to pin: ' + this.config.pin); /* first time we reach target, fire the callback */ if (this.angle != prevAngle && this.angle == this.target) @@ -104,13 +103,11 @@ function LeptonPanTiltCameraConnector(id) { this._tiltServo = null; this._panServo = null; - this._updateCount = 0; this._picam = null; } _util.inherits(LeptonPanTiltCameraConnector, PollingConnector); - /** * Resets the camera. * @class LeptonPanTiltCameraConnector @@ -118,16 +115,22 @@ _util.inherits(LeptonPanTiltCameraConnector, PollingConnector); * @private */ LeptonPanTiltCameraConnector.prototype._resetCamera = function() { - //var camera = this._camera; - //this._camera = null; + + var pantilt = this; + var def = _q.defer(); this._logger.info('Starting camera reset'); _wiringPi.digitalWrite(this._config.cameraResetPin, 0); + setTimeout(function() { - this._logger.info('Camera reset complete'); - _wiringPi.digitalWrite(this._config.cameraResetPin, 1); - //this._camera = camera; - }.bind(this), this._config.cameraResetTimeout); + pantilt._logger.info('Camera reset complete'); + _wiringPi.digitalWrite(pantilt._config.cameraResetPin, 1); + + return def.resolve('reset'); + + }.bind(pantilt), pantilt._config.cameraResetTimeout); + + return def.promise; }; /** @@ -185,23 +188,12 @@ LeptonPanTiltCameraConnector.prototype._start = function() { this._config.scanUpdateFrequency = DEFAULT_SCANUPDATE_FREQ; } - if(typeof this._config.recaptureMaxRetries !== 'number' || - this._config.recaptureMaxRetries <= 0) { - this._config.recaptureMaxRetries = DEFAULT_RECAPTURE_MAXTRIES; - } - - if(typeof this._config.recaptureTime !== 'number' || - this._config.recaptureTime <= 0) { - this._config.recaptureTime = DEFAULT_RECAPTURE_DELAY; - } - try { this._stop().fin(function() { this._logger.info( this._config.tiltServo ); /* setup servos */ - console.log('----iinitializing servos...'); this._tiltServo = new Servo( this._config.tiltServo ); this._panServo = new Servo( this._config.panServo ); @@ -255,7 +247,7 @@ LeptonPanTiltCameraConnector.prototype._stop = function() { this._logger.info('Stopping connector'); var def = _q.defer(); try { - this._setEnableLights( false ); + this._setEnableLights( false ); if(this._camera) { this._logger.info('Closing camera on: [%s]', this._config.spiDevice); @@ -286,8 +278,6 @@ LeptonPanTiltCameraConnector.prototype._tilt = function() { var angle = this._config.moves[ this._tiltServo.index ].tilt; this._tiltServo.move( angle, this._tiltFinished.bind(this)); - - this._logger.info('_tilt - ' + this._tiltServo.index + '/' + angle); }; /** @@ -301,13 +291,13 @@ LeptonPanTiltCameraConnector.prototype._tiltFinished = function() { var nPans = this._config.moves[ this._tiltServo.index ].pans.length; if (this._panServo.index > 0) { - this._panServo.index = nPans-1; + this._panServo.index = nPans-1; this._panServo.indexIncrement = -1; } else { - this._panServo.index = 0; - this._panServo.indexIncrement = 1; + this._panServo.index = 0; + this._panServo.indexIncrement = 1; } this._pan(); @@ -337,71 +327,52 @@ LeptonPanTiltCameraConnector.prototype._pan = function() { LeptonPanTiltCameraConnector.prototype._panFinished = function() { this._logger.info('_panFinished - ' + this._panServo.index + '/' + this._panServo.angle); + var pantilt = this; + this._capture(); + } -/** - * @class LeptonPanTiltCameraConnector - * @method _capture - * @protected - */ LeptonPanTiltCameraConnector.prototype._capture = function() { + this._row = this._tiltServo.index; + this._col = this._panServo.index; + var pantilt = this; + var payload = pantilt._captureIR(); - if (this._camera == null) return; - - console.log('capturing....'); - - var tIndex = this._tiltServo.index; - var pIndex = this._panServo.index; - var tAngle = this._tiltServo.angle; - var pAngle = this._panServo.angle; - - this._logger.info('_capture - tilt: '+ tIndex + '/' + tAngle + ' pan: ' + pIndex + '/' + pAngle ); - - this._row = tIndex; - this._col = pIndex; - - // First capture the IR Image. - var payload = pantilt._doIRCapture(); - - if (payload == null) + if (payload != null) { - pantilt._abortScan(); - return; - } - - if (pantilt._config.saveToFile) { - pantilt._irPacketsToFile( payload.data.camera.lines ); - } - if (!pantilt._config.rgbEnabled) + if (!pantilt._config.rgbEnabled) + { + pantilt.emit('data', payload); + pantilt._captureFinished(); + } + else + { + pantilt._captureRGB( payload ).then( + + function success(payload) + { + pantilt.emit('data', payload); + pantilt._captureFinished(); + }, + + function fail(results) + { + pantilt._abortScan(); + } + ); + } + } + else { - pantilt._logger.info('Emitting sensor data for node'); - pantilt.emit('data', payload); - pantilt._captureFinished(); - return; - } - - var rgbPromise = pantilt._doRGBCapture(); - - rgbPromise.then( - function success(image) { - pantilt._logger.info('Emitting sensor data for node'); - - // encode this image with 64 hex - payload.data.camera.rgbimage = image.toString('base64'); - pantilt.emit('data', payload); - - pantilt._captureFinished(); - }, - function fail(error) { - pantilt._logger.info('Fail to capture RGB image. Error: ' + error); + pantilt._resetCamera().then( function() { pantilt._abortScan(); - } - ); -}; + }); + } +} /** * @class LeptonPanTiltCameraConnector @@ -413,29 +384,29 @@ LeptonPanTiltCameraConnector.prototype._captureFinished = function() { var tIndex = this._tiltServo.index; var pIndex = this._panServo.index; - var nPans = this._config.moves[ tIndex ].pans.length; + var nPans = this._config.moves[ tIndex ].pans.length; - var tNext = tIndex + this._tiltServo.indexIncrement; - var pNext = pIndex + this._panServo.indexIncrement; + var tNext = tIndex + this._tiltServo.indexIncrement; + var pNext = pIndex + this._panServo.indexIncrement; /* reached the end of pan-sweep */ - if (pNext < 0 || pNext == nPans) { - - /* reached the end of scan. */ - if (tNext < 0 || tNext == this._config.moves.length) - { - this._scanFinished(); - } - else - { - this._tiltServo.index += this._tiltServo.indexIncrement; - this._tilt(); - } + if (pNext < 0 || pNext == nPans) + { + /* reached the end of scan. */ + if (tNext < 0 || tNext == this._config.moves.length) + { + this._scanFinished(); + } + else + { + this._tiltServo.index += this._tiltServo.indexIncrement; + this._tilt(); + } } else { this._panServo.index += this._panServo.indexIncrement; - this._pan(); + this._pan(); } } @@ -446,30 +417,27 @@ LeptonPanTiltCameraConnector.prototype._captureFinished = function() { */ LeptonPanTiltCameraConnector.prototype._startScan = function() { this._logger.info(''); - this._logger.info('---------------------'); this._logger.info('_startScan'); this._scanning = true; this._scan = _shortId.generate(); - this._logger.info('tilt: ' + this._tiltServo.index + '/' + this._tiltServo.angle); - this._logger.info('pan: ' + this._panServo.index + '/' + this._panServo.angle); - if (this._tiltServo.index > 0) { - this._tiltServo.index = this._config.moves.length -1; - this._tiltServo.indexIncrement = -1; + this._tiltServo.index = this._config.moves.length -1; + this._tiltServo.indexIncrement = -1; } else { - this._tiltServo.index = 0; + this._tiltServo.index = 0; this._tiltServo.indexIncrement = 1; } this._tilt(); - if (this._scanUpdateTimer == null) { - this._scanUpdateTimer = setInterval( this._updateScan.bind(this), this._config.scanUpdateFrequency ); + if (this._scanUpdateTimer == null) + { + this._scanUpdateTimer = setInterval( this._updateScan.bind(this), this._config.scanUpdateFrequency ); } } @@ -479,8 +447,6 @@ LeptonPanTiltCameraConnector.prototype._startScan = function() { * @protected */ LeptonPanTiltCameraConnector.prototype._abortScan = function() { - this._logger.info('_abortScan'); - this._tiltServo.reset(); this._panServo.reset(); this._scanning = false; @@ -494,9 +460,8 @@ LeptonPanTiltCameraConnector.prototype._abortScan = function() { * @protected */ LeptonPanTiltCameraConnector.prototype._scanFinished = function() { - this._logger.info('_scanFinished scanid: ' + this._scan); - - this._scanning = false; + this._logger.info('_scanFinished scanid: ' + this._scan); + this._scanning = false; } /** @@ -506,8 +471,7 @@ LeptonPanTiltCameraConnector.prototype._scanFinished = function() { */ LeptonPanTiltCameraConnector.prototype._updateScan = function() { - this._updateCount += 1; - + // update the servos during active scans if (this._scanning) { this._tiltServo.update(); @@ -522,57 +486,24 @@ LeptonPanTiltCameraConnector.prototype._updateScan = function() { */ LeptonPanTiltCameraConnector.prototype._process = function() { - this._logger.info('_process scanning: ' + this._scanning + ' updatecounts: ' + this._updateCount); - this._updateCount = 0; - + // do not interrupt active scan. if (this._scanning) { - return; + return; } - // start scan process + // start a new scan. this._startScan(); } - /** - * @class LeptonPanTiltCameraConnector - * @method __doIRCapture - * @protected - */ -LeptonPanTiltCameraConnector.prototype._doIRCapture = function() { - - this._logger.info("_doIRCapture"); - - var data = null; - while(true) - { - data = this._captureIR(); - if (data != null) { - break; - } - - if (this._captureRetries == this._config.recaptureMaxRetries) { - this._logger.info('Maximum recapture retries reached - aborting scan.'); - break; - } - - console.log('trying ...', this._captureRetries); - setTimeout(this._doIRCapture.bind(this), this._config.recaptureTime); - this._captureRetries += 1; - } - - return data; -}; - - /** * @class LeptonPanTiltCameraConnector - * @method _process + * @method _captureIR * @protected */ LeptonPanTiltCameraConnector.prototype._captureIR = function() { - this._logger.info('_captureIR scan: '+ this._scan + ' row: '+ this._row + ' col: ' + this._col); + var deferred = _q.defer(); if(this._camera) { @@ -588,8 +519,7 @@ LeptonPanTiltCameraConnector.prototype._captureIR = function() { rows: 0, cols: 0, delta: 0, - scan: this._scan, - row: this._row, + col: this._col }; do { @@ -639,7 +569,8 @@ LeptonPanTiltCameraConnector.prototype._captureIR = function() { }.bind(this)); } while(packets.length < 60 && !abort); - if(!abort) { + if(!abort) + { // Two bytes per column value. metadata.cols = metadata.cols/2; metadata.rows = packets.length; @@ -652,21 +583,28 @@ LeptonPanTiltCameraConnector.prototype._captureIR = function() { camera: { metadata: metadata, lines: packets, - rgbdata: null + rgbdata: null } } }; - //this._logger.info('Emitting sensor data for node'); - //this.emit('data', payload); + // save a copy of the thermal image to file + if (this._config.saveToFile) + { + this._irPacketsToFile( payload.data.camera.lines ); + } return payload; - } else { - this._logger.warn('Error reading frame from camera. No data to send'); - this._resetCamera(); + } + else + { + var errorMsg = 'Error reading frame from camera. No data to send'; + this._logger.warn( errorMsg ); } - } else { + } + else + { this._logger.warn('Camera not initialized and ready'); } @@ -674,9 +612,7 @@ LeptonPanTiltCameraConnector.prototype._captureIR = function() { }; -LeptonPanTiltCameraConnector.prototype._doRGBCapture = function() { - - this._logger.info("_doRGBCapture()"); +LeptonPanTiltCameraConnector.prototype._captureRGB = function(payload) { var deferred = _q.defer(); var pantilt = this; @@ -688,13 +624,13 @@ LeptonPanTiltCameraConnector.prototype._doRGBCapture = function() { options.settings.width = this._config.rgbWidth;; options.settings.height = this._config.rgbHeight;; options.settings.timeout = 1; + options.preview = ['none']; if (pantilt._config.saveToFile) { var tIndex = pantilt._tiltServo.index; var pIndex = pantilt._panServo.index; var tAngle = pantilt._config.moves[ tIndex ].tilt; var pAngle = pantilt._config.moves[ tIndex ].pans[ pIndex ]; - options.settings.outputPath = "./data/rgb_image_" + tAngle + "_" + pAngle + ".jpg"; } @@ -707,7 +643,8 @@ LeptonPanTiltCameraConnector.prototype._doRGBCapture = function() { pantilt._picam.on('snapped', function(results) { pantilt._setEnableLights( false ); - return deferred.resolve(results.image); + payload.data.camera.rgbimage = results.image.toString('base64'); + return deferred.resolve(payload); }); // handle errors @@ -739,7 +676,6 @@ LeptonPanTiltCameraConnector.prototype._irPacketsToFile = function( packets ) { var pAngle = this._config.moves[ tIndex ].pans[ pIndex ]; var path = "./data/ir_image_" + tAngle + "_" + pAngle + ".dat"; - console.log('captured: ' + path ); _fs.writeFile(path, JSON.stringify(packets), function(err) { diff --git a/app/utils/node-picam/lib/StillOptions.js b/app/utils/node-picam/lib/StillOptions.js index 6875d73..761a63e 100644 --- a/app/utils/node-picam/lib/StillOptions.js +++ b/app/utils/node-picam/lib/StillOptions.js @@ -109,13 +109,13 @@ Options.prototype.toArray = function () { if (isDefined(opts.preview) && opts.preview.length == 1) { var val = opts.preview[0]; - var previewFlag = options.PREVIEW[val]; + //var previewFlag = opts.PREVIEW[val]; if (val == "none") { result.push("--nopreview"); } else if (val == "fullscreen") { result.push("--fullscreen"); - } else if (!previewFlag) { - result.push(val); + } //else if (!previewFlag) { + else { result.push(val); //TODO: [medium] (nhat) - validate the x,y,w,h string } } diff --git a/config/config.json b/config/config.json index de1c7af..a9171d2 100644 --- a/config/config.json +++ b/config/config.json @@ -20,14 +20,12 @@ "lepton-pan-tilt-camera0": { "type": "LeptonPanTiltCamera", "config": { - "saveToFile":false, + "saveToFile":true, "scanUpdateFrequency":50, - "recaptureMaxRetries":2, - "recaptureTime":1000, "pollFrequency": 6000, "spiDevice": "/dev/spidev0.1", "i2cDevice": "/dev/i2c-1", - "maxRetries": 200, + "maxRetries": 500, "cameraResetTimeout":1000, "cameraResetPin": 23, "cameraLightsPin": 4, @@ -50,15 +48,15 @@ }, "moves": [ { - "tilt": 80, + "tilt": 100, "pans": [60,70,80,90,100] - }, { - "tilt": 65, + }, { + "tilt": 80, "pans": [60,70,80,90,100] }, { - "tilt": 50, + "tilt": 60, "pans": [60,70,80,90,100] - } ] + }] } } }, @@ -137,13 +135,13 @@ "moves": [ { "tilt": 50, - "pans": [60,70,80,90,100] + "pans": [40,50,60,70,80,90,100] }, { "tilt": 65, - "pans": [60,70,80,90,100] + "pans": [40,50,60,70,80,90,100] }, { "tilt": 80, - "pans": [60,70,80,90,100] + "pans": [40,50,60,70,80,90,100] } ] } },