diff --git a/config.js b/config.js index 9b128c8..37969ed 100644 --- a/config.js +++ b/config.js @@ -62,9 +62,8 @@ module.exports = { "vflip": false, "shutter": null, "drc": null, - "mode": 1, + "mode": 0, "bitrate": null, - "width": 400, "height": 400, "framerate": 10, diff --git a/lib/camera/index.js b/lib/camera/index.js index 30c90c2..874dc7f 100644 --- a/lib/camera/index.js +++ b/lib/camera/index.js @@ -116,7 +116,7 @@ class Camera extends EventEmitter { } startRecording() { - var outputPath = '/home/pi/output.h264'; + var outputPath = 'output.h264'; var opts = this.config.record; if (!this.config.previewWhenRecording) { @@ -165,13 +165,21 @@ class Camera extends EventEmitter { reject(err); }); + var exited = false; recorder.on('exit', function (code) { if (code === 0) { resolve(); } else { reject(code); } + exited = true; }); + + setTimeout(function () { + if (!exited) { + recorder.kill(); + } + }, 5000); }); } } @@ -202,4 +210,3 @@ Camera.defaultOptions = { }; module.exports = Camera; - diff --git a/lib/camera/stream.js b/lib/camera/stream.js index 1164b99..ba81166 100644 --- a/lib/camera/stream.js +++ b/lib/camera/stream.js @@ -1,4 +1,5 @@ var child = require('child_process'); +var cmdExists = require('cmd-exists-sync'); var raspivid = function (options) { options = options || {}; @@ -29,48 +30,64 @@ var raspivid = function (options) { }); }; -// Doesn't support non-seekable streams with mp4 -var avconv = function () { +var avconv = function (options) { + options = options || {}; + var args = [ - '-i', - 'pipe:0', - '-f', - 'mp4', - '-loglevel', - 'error', // See http://blog.jungkyungsuk.com/tag/loglevel/ - 'pipe:1' + '-f', 'video4linux2', + '-r', '30000/1001', + '-i', '/dev/video0', + //'-b:a', '2M', // ? + '-bt', '4M', + '-vcodec', 'libx264', + //'-pass', '1', + '-profile:v', 'baseline', + '-coder', '0', + '-bf', '0', + '-flags', '-loop', '-an', + '-bsf:v', 'h264_mp4toannexb', + '-f', 'h264' ]; - return child.spawn('avconv', args, { - stdio: ['pipe', 'pipe', 'inherit'] - }); -}; + if (options.timeout) { + args.push('-t'); + args.push(options.timeout); + } -// Doesn't support mp4 streaming -var cvlc = function () { - var args = [ - '-I', - 'dummy', - '-v', - 'v4l2:///dev/video0', - '--v4l2-chroma', - 'h264', - '--v4l2-width', - '800', - '--v4l2-height', - '600', - '--sout', - //'#standard{access=file,mux=mp4,dst=-}', - '#standard{access=http,mux=ts,dst=0.0.0.0:3001}', // TODO - ':demux=h264' - ]; + if (options.width && options.height) { + args.push('-vf'); + args.push('scale='+options.width+':'+options.height); + } - // cvlc v4l2:///dev/video0 --v4l2-width 800 --v4l2-height 600 --v4l2-chroma h264 --sout '#standard{access=http,mux=ts,dst=0.0.0.0:12345}' -vvv + if (options.framerate) { + args.push('-r'); + args.push(options.framerate); + } + + if (options.bitrate) { + args.push('-b'); + args.push(options.bitrate); + } - return child.spawn('cvlc', args, { + if (options.output) { + args.push(options.output); + } else { + args.push('-'); + } + + console.log('avconv', args.join(' ')); + + return child.spawn('avconv', args, { stdio: ['pipe', 'pipe', 'inherit'] }); }; -module.exports = raspivid; - +if (cmdExists('raspivid')) { + module.exports = raspivid; +} else if (cmdExists('avconv')) { + module.exports = avconv; +} else { + module.exports = function () { + throw new Error('No recorder available'); + }; +} diff --git a/lib/controller.js b/lib/controller.js index 96e6586..6517053 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -5,14 +5,16 @@ var path = require('path'); var EventEmitter = require('events').EventEmitter; function loadUpdaters() { - var dir = 'controller'; - - var updaters = {}; - fs.readdirSync(__dirname+'/'+dir).forEach(function (file) { - var name = path.basename(file, '.js'); - updaters[name] = require('./'+dir+'/'+name); - }); - return updaters; + var dir = 'controller'; + + var updaters = {}; + fs.readdirSync(__dirname+'/'+dir).forEach(function (file) { + var name = path.basename(file, '.js'); + if (name[0] === '_') return; + + updaters[name] = require('./'+dir+'/'+name); + }); + return updaters; } var updaters = loadUpdaters(); diff --git a/lib/controller/base.js b/lib/controller/_base.js similarity index 100% rename from lib/controller/base.js rename to lib/controller/_base.js diff --git a/lib/controller/dummy.js b/lib/controller/dummy.js index 0343221..968b3cb 100644 --- a/lib/controller/dummy.js +++ b/lib/controller/dummy.js @@ -1,6 +1,6 @@ 'use strict'; -var BaseUpdater = require('./base'); +var BaseUpdater = require('./_base'); /** * A dummy updater. diff --git a/lib/controller/physics-quantif.js b/lib/controller/physics-quantif.js index 118d858..a0a49a0 100644 --- a/lib/controller/physics-quantif.js +++ b/lib/controller/physics-quantif.js @@ -1,6 +1,6 @@ 'use strict'; -var BaseUpdater = require('./base'); +var BaseUpdater = require('./_base'); /** * The physics updater. diff --git a/lib/controller/physics.js b/lib/controller/physics.js index 1c648ee..aff7b6f 100644 --- a/lib/controller/physics.js +++ b/lib/controller/physics.js @@ -1,6 +1,6 @@ 'use strict'; -var BaseUpdater = require('./base'); +var BaseUpdater = require('./_base'); /** * The physics updater. diff --git a/lib/controller/rate-simple.js b/lib/controller/rate-simple.js index 590f8bc..9d3593d 100644 --- a/lib/controller/rate-simple.js +++ b/lib/controller/rate-simple.js @@ -1,7 +1,7 @@ 'use strict'; var PidController = require('node-pid-controller'); -var BaseUpdater = require('./base'); +var BaseUpdater = require('./_base'); /** * A simple rate updater. diff --git a/lib/controller/stabilize-simple.js b/lib/controller/stabilize-simple.js index fea2b15..7e3cb97 100644 --- a/lib/controller/stabilize-simple.js +++ b/lib/controller/stabilize-simple.js @@ -1,7 +1,7 @@ 'use strict'; var PidController = require('node-pid-controller'); -var BaseUpdater = require('./base'); +var BaseUpdater = require('./_base'); var forEachPid = Symbol(); diff --git a/lib/quadcopter-base.js b/lib/quadcopter-base.js index dd0adcf..3801f27 100644 --- a/lib/quadcopter-base.js +++ b/lib/quadcopter-base.js @@ -1,6 +1,7 @@ 'use strict'; var EventEmitter = require('events').EventEmitter; +var extend = require('extend'); var Controller = require('./controller'); /** @@ -50,8 +51,8 @@ class QuadcopterBase extends EventEmitter { set config(config) { if (typeof config != 'object') throw new Error('Cannot set quadcopter "config" property: must be an object'); - this._config = config; - this.emit('config', config); + extend(this._config, config); + this.emit('config', this.config); } get power() { diff --git a/lib/quadcopter.js b/lib/quadcopter.js index f4645f8..f4f5dc5 100644 --- a/lib/quadcopter.js +++ b/lib/quadcopter.js @@ -36,13 +36,13 @@ class Quadcopter extends QuadcopterBase { that._resetMotorsSpeeds(true); }, function (err) { - console.error('WARN: cannot load motors', err); + console.error('WARN: cannot load motors:', err); }); // Then Inertial Measurement Unit console.log('Loading sensors...'); var promise = loadSensors(this).catch(function (err) { - console.error('WARN: cannot load sensors', err); + console.error('WARN: cannot load sensors:', err); }).then(function (sensors) { that.sensors = sensors || {}; if (that.sensors.mpu6050) { @@ -58,7 +58,7 @@ class Quadcopter extends QuadcopterBase { return that.camera.check().then(function () { that.features.push('camera'); }, function (err) { - console.error('WARN: camera not available', err); + console.error('WARN: camera not available:', err); }); }).then(function () { // Ready :-) diff --git a/package.json b/package.json index fd5802a..5868676 100644 --- a/package.json +++ b/package.json @@ -20,22 +20,27 @@ "dependencies": { "bootstrap": "^3.3.6", "broadway-player": "^0.1.1", + "browser-request": "^0.3.3", "c3": "^0.4.11-rc4", + "cmd-exists-sync": "^0.1.0", "events": "^1.0.2", + "expand-flatten": "^1.0.0", "express": "^4.12.3", "express-browserify-lite": "^0.1.2", "express-ws": "^0.2.6", "extend": "^2.0.1", + "global": "^4.3.0", "i2c-bus": "^0.11.1", "i2c-mpu6050": "^2.2.0", - "jquery": "^2.2.0", + "mercury": "^14.1.0", "mpu6050-dmp": "^0.0.6", "msgpack5": "^3.3.0", "node-pid-controller": "^0.1.2", + "round-to": "^1.1.0", "servoblaster": "^0.1.0", "smoothie": "^1.27.0", "stream-split": "^0.2.0", - "svg-injector": "^1.1.3", + "throttleit": "^1.0.0", "through": "^2.3.8", "util": "^0.10.3" } diff --git a/public/assets/main.css b/public/assets/main.css index 090371c..587187b 100644 --- a/public/assets/main.css +++ b/public/assets/main.css @@ -76,6 +76,9 @@ template { box-shadow: none; border-bottom-color: rgba(0, 100, 200, 0.3); } +.form-control:invalid { + border-bottom-color: rgba(255, 0, 0, 0.2); +} input[type="number"].form-control { -moz-appearance: textfield; } @@ -83,6 +86,9 @@ input[type="number"].form-control { .form-inline input[type="number"].form-control { width: 65px; } +.form-inline input[type="number"][size="1"].form-control { + width: 35px; +} #console { height: 200px; @@ -98,7 +104,7 @@ input[type="number"].form-control { color: rgb(0, 100, 0); } -.joystick { +.direction-mouse { position: relative; width: 300px; height: 300px; @@ -106,7 +112,7 @@ input[type="number"].form-control { border-radius: 50%; margin: 10px auto; } -.joystick .handle { +.direction-mouse .handle { position: absolute; width: 16px; height: 16px; @@ -115,7 +121,17 @@ input[type="number"].form-control { opacity: 0.2; pointer-events: none; } -.joystick.active .handle { +.direction-mouse .handle .tooltip { + width: 100px; + top: -35px; + left: -42px; +} +.direction-mouse:active .handle, +.direction-mouse.active .handle { + opacity: 1; +} +.direction-mouse:active .handle .tooltip, +.direction-mouse.active .handle .tooltip { opacity: 1; } @@ -150,10 +166,8 @@ input[type="range"][orient="vertical"]::-moz-range-track { width: 5px; } -#power-input { - width: 100px; +.power-input { height: 300px; - margin: auto; } select { @@ -242,21 +256,24 @@ select { border-color: rgba(0, 100, 200, 0.3); } -.quadcopter-schema { +.quadcopter-outline { + margin: auto; +} +.quadcopter-outline svg { fill: rgb(58, 58, 58); transition: 0.2s transform; } -.quadcopter-schema g { +.quadcopter-outline g { transition: 0.2s fill; } -#quadcopter-top { +#quadcopter-outline-top { width: 300px; height: 300px; } -.quadcopter-side { +.quadcopter-outline-side { width: 300px; height: 75px; - margin: 37px 0; + margin-top: 37px; } #camera-video canvas { diff --git a/public/index.html b/public/index.html index a7a1460..02b61a5 100644 --- a/public/index.html +++ b/public/index.html @@ -8,502 +8,13 @@ - + +
- - -
- Load average:
- Memory:
-
- Motors speed (x10µs):
-
-
- Motors forces (N):
-
-
- Gyro (°/s):
- Accel (g):
- Rotation (°):
- Temperature (°C):
-