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 238494f..eaba2ff 100755 --- a/app/connectors/lepton-pan-tilt-connector.js +++ b/app/connectors/lepton-pan-tilt-connector.js @@ -1,33 +1,41 @@ /* 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 PiCamera = require('../utils/node-picam/lib/Camera').Camera; 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(); }; Servo.prototype.reset = function() { - this.angle = 0; + this.angle = this.config.minAngle; this.target = 0; this.direction = 1; this.callback = null; this.index = 0; this.indexIncrement = 1; + this.rgbRetries = RGB_MAX_READ_RETRIES; }; Servo.prototype.move = function(angle, callback) { @@ -35,7 +43,6 @@ Servo.prototype.move = function(angle, callback) { this.target = angle; this.callback = callback; - if (this.target < this.angle) { this.direction = -1; @@ -47,7 +54,6 @@ Servo.prototype.move = function(angle, callback) { if (this.angle == this.target && this.callback != null) { this.callback(); - //this.callback = null; } }; @@ -65,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) @@ -73,7 +78,6 @@ Servo.prototype.update = function() { if (this.callback != null) { this.callback(); - //this.callback = null; } } }; @@ -91,18 +95,19 @@ 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; this._panServo = null; - this._updateCount = 0; + this._picam = null; } _util.inherits(LeptonPanTiltCameraConnector, PollingConnector); - /** * Resets the camera. * @class LeptonPanTiltCameraConnector @@ -110,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), 500); + pantilt._logger.info('Camera reset complete'); + _wiringPi.digitalWrite(pantilt._config.cameraResetPin, 1); + + return def.resolve('reset'); + + }.bind(pantilt), pantilt._config.cameraResetTimeout); + + return def.promise; }; /** @@ -152,6 +163,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; @@ -172,40 +188,25 @@ 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 ); - - console.log('tiltservo:'); - console.log( this._tiltServo ); - - console.log('panservo:'); - console.log( this._panServo ); - + /* setup 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.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); @@ -246,6 +247,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. @@ -275,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); }; /** @@ -290,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(); @@ -326,41 +327,53 @@ LeptonPanTiltCameraConnector.prototype._pan = function() { LeptonPanTiltCameraConnector.prototype._panFinished = function() { this._logger.info('_panFinished - ' + this._panServo.index + '/' + this._panServo.angle); - this._tryCapture(); -} + var pantilt = this; -/** - * @class LeptonPanTiltCameraConnector - * @method _tryCapture - * @protected - */ -LeptonPanTiltCameraConnector.prototype._tryCapture = function() { + this._capture(); - 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 ); +LeptonPanTiltCameraConnector.prototype._capture = function() { - if (this._captureRetries == this._config.recaptureMaxRetries) { - this._logger.info('Maximum recapture retries reached - aborting scan.'); - this._abortScan(); - return; - } + this._row = this._tiltServo.index; + this._col = this._panServo.index; + + var pantilt = this; + var payload = pantilt._captureIR(); - if (this._capture(this._scanId, tIndex, pIndex)) + if (payload != null) { - this._captureRetries = 0; - this._captureFinished(); - } - else + + 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 { - this._captureRetries += 1; - setTimeout( this._tryCapture.bind(this), this._config.recaptureTime ); + pantilt._resetCamera().then( function() { + pantilt._abortScan(); + }); } -}; - +} + /** * @class LeptonPanTiltCameraConnector * @method _captureFinished @@ -371,58 +384,32 @@ 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(); } } -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 @@ -430,30 +417,27 @@ LeptonPanTiltCameraConnector.prototype._captureFinishedOLD = function() { */ LeptonPanTiltCameraConnector.prototype._startScan = function() { this._logger.info(''); - this._logger.info('---------------------'); this._logger.info('_startScan'); this._scanning = true; - this._scanId = _shortId.generate(); - - this._logger.info('tilt: ' + this._tiltServo.index + '/' + this._tiltServo.angle); - this._logger.info('pan: ' + this._panServo.index + '/' + this._panServo.angle); + this._scan = _shortId.generate(); 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 ); } } @@ -463,11 +447,11 @@ LeptonPanTiltCameraConnector.prototype._startScan = function() { * @protected */ LeptonPanTiltCameraConnector.prototype._abortScan = function() { - this._logger.info('_abortScan'); - this._tiltServo.reset(); this._panServo.reset(); this._scanning = false; + this._row = 0; + this._col = 0; } /** @@ -476,9 +460,8 @@ LeptonPanTiltCameraConnector.prototype._abortScan = function() { * @protected */ LeptonPanTiltCameraConnector.prototype._scanFinished = function() { - this._logger.info('_scanFinished scanid: ' + this._scanId); - - this._scanning = false; + this._logger.info('_scanFinished scanid: ' + this._scan); + this._scanning = false; } /** @@ -487,17 +470,13 @@ LeptonPanTiltCameraConnector.prototype._scanFinished = function() { * @protected */ LeptonPanTiltCameraConnector.prototype._updateScan = function() { - //this._logger.info('_updateScan'); - - this._updateCount += 1; + // update the servos during active scans if (this._scanning) { this._tiltServo.update(); this._panServo.update(); } - - //this._scanUpdateTimer = setTimeout( this._updateScan.bind(this), this._config.scanUpdateFrequency ); } /** @@ -507,26 +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 _process + * @method _captureIR * @protected */ -LeptonPanTiltCameraConnector.prototype._capture = function(scanId, row, col) { +LeptonPanTiltCameraConnector.prototype._captureIR = function() { - this._logger.info('_capture scan: '+ scanId + ' row: '+ row + ' col: ' + col); + var deferred = _q.defer(); if(this._camera) { @@ -542,8 +519,8 @@ LeptonPanTiltCameraConnector.prototype._capture = function(scanId, row, col) { rows: 0, cols: 0, delta: 0, - scanId: scanId, - position: row + ',' + col + + col: this._col }; do { var txBuf = new Buffer(PACKET_SIZE); @@ -592,7 +569,8 @@ LeptonPanTiltCameraConnector.prototype._capture = function(scanId, row, col) { }.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; @@ -604,25 +582,110 @@ 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); + // save a copy of the thermal image to file + if (this._config.saveToFile) + { + this._irPacketsToFile( payload.data.camera.lines ); + } - return true; + 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'); } - return false; + return null; +}; + + +LeptonPanTiltCameraConnector.prototype._captureRGB = function(payload) { + + var deferred = _q.defer(); + var pantilt = this; + + this._setEnableLights( true ); + + var options = PiCamera.DEFAULT_PROFILE.opts; + options.controls.flipVertical = false; + 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"; + } + + this._picam = new PiCamera( options ); + + // take the picture + pantilt._picam.takeJPG(); + + // handle snapped event (i.e when the picture data is fully captured) + pantilt._picam.on('snapped', function(results) + { + pantilt._setEnableLights( false ); + payload.data.camera.rgbimage = results.image.toString('base64'); + return deferred.resolve(payload); + }); + + // handle errors + pantilt._picam.on('error', function(results) + { + pantilt._setEnableLights( false ); + return deferred.reject(results.error); + }); + + return deferred.promise +}; + +LeptonPanTiltCameraConnector.prototype._setEnableLights = function( on ) { + if (on) + { + _wiringPi.digitalWrite(this._config.cameraLightsPin, _wiringPi.HIGH); + } + else + { + _wiringPi.digitalWrite(this._config.cameraLightsPin, _wiringPi.LOW); + } }; +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"; + + _fs.writeFile(path, JSON.stringify(packets), + function(err) + { + if(err) + { + console.log( err ); + } + } + ); +} // packetsToFile + + module.exports = LeptonPanTiltCameraConnector; 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..46a0b84 --- /dev/null +++ b/app/utils/node-picam/lib/Camera.js @@ -0,0 +1,86 @@ +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 pid = spawn(COMMAND, 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..761a63e --- /dev/null +++ b/app/utils/node-picam/lib/StillOptions.js @@ -0,0 +1,272 @@ +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 = opts.PREVIEW[val]; + if (val == "none") { + result.push("--nopreview"); + } else if (val == "fullscreen") { + result.push("--fullscreen"); + } //else if (!previewFlag) { + else { 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); + } + + result.push("--output"); + if (isDefined(opts.settings.outputPath)) { + result.push(opts.settings.outputPath); + } + else { + result.push("-"); + } + + //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 old mode 100755 new mode 100644 index f086535..a9171d2 --- a/config/config.json +++ b/config/config.json @@ -11,25 +11,54 @@ "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": { + "saveToFile":true, + "scanUpdateFrequency":50, + "pollFrequency": 6000, + "spiDevice": "/dev/spidev0.1", + "i2cDevice": "/dev/i2c-1", + "maxRetries": 500, + "cameraResetTimeout":1000, + "cameraResetPin": 23, + "cameraLightsPin": 4, + "rgbEnabled": true, + "rgbWidth": 640, + "rgbHeight": 480, + + "tiltServo": { + "pin": 24, + "minAngle": 30, + "maxAngle": 110, + "incAngle": 1 + }, + + "panServo": { + "pin": 1, + "minAngle": 40, + "maxAngle": 120, + "incAngle": 1 + }, + + "moves": [ { + "tilt": 100, + "pans": [60,70,80,90,100] + }, { + "tilt": 80, + "pans": [60,70,80,90,100] + }, { + "tilt": 60, + "pans": [60,70,80,90,100] + }] + } + } }, "cloudConnectors.ignore": { "testgw-cnc-cloud": { @@ -106,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] } ] } }, 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 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",