Skip to content

Commit

Permalink
Added camera control webpage
Browse files Browse the repository at this point in the history
  • Loading branch information
BreeeZe committed Apr 22, 2015
1 parent 9bc1ddf commit 55c659e
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 61 deletions.
2 changes: 2 additions & 0 deletions RPOS.njsproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Content Include="cpp\rtspServer" />
<Content Include="cpp\rtspServer.cpp" />
<Content Include="cpp\rtspServer.o" />
<Content Include="views\camera.ntl" />
<Content Include="LICENSE" />
<Content Include="package.json" />
<None Include="wsdl\www.w3.org.2005.08.addressing.ws-addr.xsd" />
Expand All @@ -67,6 +68,7 @@
<Folder Include="bin\" />
<Folder Include="cpp\" />
<Folder Include="lib\" />
<Folder Include="views\" />
<Folder Include="services\" />
<Folder Include="services\stubs\" />
<Folder Include="web\" />
Expand Down
99 changes: 82 additions & 17 deletions lib/camera.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
var spawn = require('child_process').spawn;
var execSync = require('child_process').execSync;
var utils = require('./utils');
var fs = require('fs');
var v4l2 = require('./v4l2ctl');
var parser = require('body-parser');

function Camera(config) {
function Camera(config, webserver) {
this.config = config;
this.rtspServer = null;
this.load();
this.webserver = webserver;
this.setupWebserver();

$this = this;
utils.cleanup(function () {
if (!utils.isWin()) {
Expand All @@ -15,13 +21,70 @@ function Camera(config) {
});
};

Camera.prototype.setupWebserver = function () {
var $this = this;
this.webserver.use(parser.urlencoded({ extended: true }));
this.webserver.engine('ntl', function (filePath, options, callback) {
$this.getSettingsPage(filePath, callback);
});
this.webserver.set('views', './views'); // specify the views directory
this.webserver.set('view engine', 'ntl'); // register the template engine
this.webserver.get('/', function (req, res) {
res.render('camera', {});
});
this.webserver.post('/', function (req, res) {
for (var par in req.body) {
var g = par.split('.')[0];
var p = par.split('.')[1];
if (p && g) {
var prop = v4l2.Controls[g][p];
prop.set(req.body[par]);
if (prop.isDirty())
utils.log.debug("Property %s changed to %s", par, prop.get());
}
}
res.render('camera', {});
});
};

Camera.prototype.getSettingsPage = function (filePath, callback) {
fs.readFile(filePath, function (err, content) {
if (err)
return callback(new Error(err));

var html = "";
var usercontrols = v4l2.Controls.UserControls;
var codeccontrols = v4l2.Controls.CodecControls;
var jpgcontrols = v4l2.Controls.JPEGCompressionControls;

html += "<tr><td colspan='2'><strong>User Controls</strong></td></tr>";
for (var uc in usercontrols) {
html += ['<tr><td><span class="label">', uc, '</span></td><td><input type="text" name="UserControls.', uc, '" value="', usercontrols[uc].get(), '" />', '</td><tr>'].join('');
}

html += "<tr><td colspan='2'><strong>Codec Controls</strong></td></tr>";

for (var cc in codeccontrols) {
html += ['<tr><td><span class="label">', cc, '</span></td><td><input type="text" name="CodecControls.', cc, '" value="', codeccontrols[cc].get(), '" />', '</td><tr>'].join('');
}

html += "<tr><td colspan='2'><strong>JPG Compression Controls</strong></td></tr>";

for (var jc in jpgcontrols) {
html += ['<tr><td><span class="label">', jc, '</span></td><td><input type="text" name="JPEGCompressionControls.', jc, '" value="', jpgcontrols[jc].get(), '" />', '</td><tr>'].join('');
}
var rendered = content.toString().replace('{{row}}', html);
return callback(null, rendered);
})
}

Camera.prototype.load = function () {
if (!utils.isWin()) {
utils.log.debug(execSync("sudo modprobe bcm2835-v4l2"));
} else {
utils.log.debug("Would load driver");
}
}
};

Camera.prototype.options = {
resolutions : [
Expand Down Expand Up @@ -65,25 +128,27 @@ Camera.prototype.settings = {

Camera.prototype.setSettings = function (newsettings) {
if (!utils.isWin()) {
utils.log.debug(
execSync(["sudo v4l2-ctl --set-fmt-video=",
"width=", newsettings.resolution.Width,
",height=", newsettings.resolution.Height,
",pixelformat=4"].join('')));

utils.log.debug(execSync("sudo v4l2-ctl --set-fmt-video=width=" + newsettings.resolution.Width + ",height=" + newsettings.resolution.Height + ",pixelformat=4"));
utils.log.debug(execSync("sudo v4l2-ctl --set-ctrl " +
"video_bitrate=" + (newsettings.bitrate * 1000) +
",video_bitrate_mode=" + (newsettings.quality > 0 ? 0 : 1) +
",h264_i_frame_period=" + (this.settings.forceGop ? this.settings.gop : newsettings.gop) +
",horizontal_flip=" + (this.settings.hf ? 1 : 0) +
",vertical_flip=" + (this.settings.vf ? 1 : 0)
));
utils.log.debug(execSync("sudo v4l2-ctl --set-parm=" + newsettings.frameRate));
utils.log.debug(
execSync(["sudo v4l2-ctl --set-ctrl ",
"video_bitrate=", (newsettings.bitrate * 1000),
",video_bitrate_mode=", (newsettings.quality > 0 ? 0 : 1),
",h264_i_frame_period=", (this.settings.forceGop ? this.settings.gop : newsettings.gop),
",horizontal_flip=", (this.settings.hf ? 1 : 0),
",vertical_flip=", (this.settings.vf ? 1 : 0)
].join('')));

utils.log.debug(
execSync(["sudo v4l2-ctl --set-parm=", newsettings.frameRate].join('')));
} else {
for (var s in newsettings)
utils.log.debug("Would set %s to %s", s, newsettings[s]);
}

//settings.frameRate;
//settings.profile;
//settings.quality;
//settings.resolution;
};

Camera.prototype.startRtsp = function (input) {
Expand All @@ -95,7 +160,7 @@ Camera.prototype.startRtsp = function (input) {

this.rtspServer = spawn("./bin/rtspServer", [
input,
"1024000",
"2088960",
this.config.RTSPPort,
0,
this.config.RTSPName]);
Expand Down
36 changes: 35 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,42 @@ Date.prototype.dst = function () {
return this.getTimezoneOffset() < this.stdTimezoneOffset();
};

var property = function (value) {
this.typeConstructor = value.constructor;
this.dirty = false;
this.value = value === undefined ? null : value;
};

property.prototype.get = function () {
return this.value;
};

property.prototype.set = function (value) {
if (this.typeConstructor) {
if (this.typeConstructor.name == "Boolean") {
value = value.toUpperCase() === "TRUE";
} else if (this.typeConstructor.name != "Object")
value = this.typeConstructor(value);
} else {
this.typeConstructor = value.constructor;
}
if (value !== this.value)
this.dirty = true;
this.value = value;
};

property.prototype.isDirty = function () {
return this.dirty;
};

property.prototype.reset = function () {
this.dirty = false;
};

var utils = {

property : property,

getSerial : function () {
// Extract serial from cpuinfo file
cpuserial = "0000000000000000"
Expand Down Expand Up @@ -65,7 +99,7 @@ var utils = {
}
},
},
cleanup : function(callback) {
cleanup : function (callback) {

// attach user callback to the process event emitter
// if no callback, it will still exit gracefully on Ctrl-C
Expand Down
60 changes: 31 additions & 29 deletions lib/v4l2ctl.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
var v4l2ctl = {
var property = require('./utils').property;

var v4l2ctl = {
Controls : {
UserControls : {
brightness: 50, // min=0 max=100 step=1 default=50 value=50 flags=slider
contrast: 0, // min=-100 max=100 step=1 default=0 value=0 flags=slider
saturation: 0, // min=-100 max=100 step=1 default=0 value=0 flags=slider
red_balance: 1000, // min=1 max=7999 step=1 default=1000 value=1000 flags=slider
blue_balance: 1000, // min=1 max=7999 step=1 default=1000 value=1000 flags=slider
horizontal_flip: false, // default=0 value=0
vertical_flip: false, // default=0 value=0
power_line_frequency: 1, // min=0 max=3 default=1 value=1 | 0: Disabled,1: 50 Hz,2: 60 Hz,3: Auto
sharpness: 0, // min=-100 max=100 step=1 default=0 value=0 flags=slider
color_effects: 0, // min=0 max=15 default=0 value=0 | 0: None,1: Black & White,2: Sepia,3: Negative,4: Emboss,5: Sketch,6: Sky Blue,7: Grass Green,8: Skin Whiten,9: Vivid,10: Aqua,11: Art Freeze,12: Silhouette,13: Solarization,14: Antique,15: Set Cb/Cr
rotate: 0, // min=0 max=360 step=90 default=0 value=0
color_effects_cbcr: 32896, // min=0 max=65535 step=1 default=32896 value=32896
brightness: new property(50), // min=0 max=100 step=1 default=50 value=50 flags=slider
contrast: new property(0), // min=-100 max=100 step=1 default=0 value=0 flags=slider
saturation: new property(0), // min=-100 max=100 step=1 default=0 value=0 flags=slider
red_balance: new property(1000), // min=1 max=7999 step=1 default=1000 value=1000 flags=slider
blue_balance: new property(1000), // min=1 max=7999 step=1 default=1000 value=1000 flags=slider
horizontal_flip: new property(false), // default=0 value=0
vertical_flip: new property(false), // default=0 value=0
power_line_frequency: new property(1), // min=0 max=3 default=1 value=1 | 0: Disabled,1: 50 Hz,2: 60 Hz,3: Auto
sharpness: new property(0), // min=-100 max=100 step=1 default=0 value=0 flags=slider
color_effects: new property(0), // min=0 max=15 default=0 value=0 | 0: None,1: Black & White,2: Sepia,3: Negative,4: Emboss,5: Sketch,6: Sky Blue,7: Grass Green,8: Skin Whiten,9: Vivid,10: Aqua,11: Art Freeze,12: Silhouette,13: Solarization,14: Antique,15: Set Cb/Cr
rotate: new property(0), // min=0 max=360 step=90 default=0 value=0
color_effects_cbcr: new property(32896), // min=0 max=65535 step=1 default=32896 value=32896
},
CodecControls : {
video_bitrate_mode: 0, // min=0 max=1 default=0 value=0 flags=update | 0: Variable Bitrate,1: Constant Bitrate
video_bitrate: 10000000, // min=25000 max=25000000 step=25000 default=10000000 value=10000000
repeat_sequence_header: false, // default=0 value=0
h264_i_frame_period: 60, // min=0 max=2147483647 step=1 default=60 value=60
h264_level: 11, // min=0 max=11 default=11 value=11 | 0:1,1:1b,2:1.1,3:1.2,4:1.3,5:2,6:2.1,7:2.2,8:3,9:3.1,10:3.2,11:4
h264_profile: 4 // min=0 max=4 default=4 value=4 | 0:Baseline,1:Constrained Baseline,2:Main,4:High
video_bitrate_mode: new property(0), // min=0 max=1 default=0 value=0 flags=update | 0: Variable Bitrate,1: Constant Bitrate
video_bitrate: new property(10000000), // min=25000 max=25000000 step=25000 default=10000000 value=10000000
repeat_sequence_header: new property(false), // default=0 value=0
h264_i_frame_period: new property(60), // min=0 max=2147483647 step=1 default=60 value=60
h264_level: new property(11), // min=0 max=11 default=11 value=11 | 0:1,1:1b,2:1.1,3:1.2,4:1.3,5:2,6:2.1,7:2.2,8:3,9:3.1,10:3.2,11:4
h264_profile: new property(4) // min=0 max=4 default=4 value=4 | 0:Baseline,1:Constrained Baseline,2:Main,4:High
},
CameraControls : {
auto_exposur: 0, // min=0 max=3 default=0 value=0
exposure_time_absolute: 1000, // min=1 max=10000 step=1 default=1000 value=1000
exposure_dynamic_framerate: false, // default=0 value=0
auto_exposure_bias: 12, // min=0 max=24 default=12 value=12
white_balance_auto_preset: 1, // min=0 max=9 default=1 value=1 | 0:Manual,1:Auto,2:Incandescent,3:Fluorescent,4:Fluorescent,5:Horizon,6:Daylight,7:Flash,8:Cloudy,9:Shade
image_stabilization: false, // default=0 value=0
iso_sensitivity: 0, // min=0 max=4 default=0 value=0 | 0: 0,1: 100,2: 200,3: 400,4: 800,
exposure_metering_mode: 0, // min=0 max=2 default=0 value=0 | 0: Average,1: Center Weighted,2: Spot,
scene_mode: 0 // min=0 max=13 default=0 value=0 | 0: None,8: Night,11: Sports
auto_exposur: new property(0), // min=0 max=3 default=0 value=0
exposure_time_absolute: new property(1000), // min=1 max=10000 step=1 default=1000 value=1000
exposure_dynamic_framerate: new property(false), // default=0 value=0
auto_exposure_bias: new property(12), // min=0 max=24 default=12 value=12
white_balance_auto_preset: new property(1), // min=0 max=9 default=1 value=1 | 0:Manual,1:Auto,2:Incandescent,3:Fluorescent,4:Fluorescent,5:Horizon,6:Daylight,7:Flash,8:Cloudy,9:Shade
image_stabilization: new property(false), // default=0 value=0
iso_sensitivity: new property(0), // min=0 max=4 default=0 value=0 | 0: 0,1: 100,2: 200,3: 400,4: 800,
exposure_metering_mode: new property(0), // min=0 max=2 default=0 value=0 | 0: Average,1: Center Weighted,2: Spot,
scene_mode: new property(0) // min=0 max=13 default=0 value=0 | 0: None,8: Night,11: Sports
},
JPEGCompressionControls : {
compression_quality: 30 // min=1 max=100 step=1 default=30 value=30
compression_quality: new property(30) // min=1 max=100 step=1 default=30 value=30
}
},

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
},
"dependencies": {
"soap": "git://github.com/BreeeZe/node-soap.git",
"cli-color" : ""
"cli-color": "1.0.0",
"express": "4.12.3",
"body-parser": "1.12.3"
}
}
12 changes: 6 additions & 6 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var utils = require('./lib/utils');
var pjson = require('./package.json');
var config = require('./config');
var http = require('http');
var express = require('express');

utils.log.level = config.logLevel;

Expand All @@ -36,13 +37,12 @@ for (var i in config.DeviceInformation) {
utils.log.info("%s : %s", i , config.DeviceInformation[i]);
}

var webserver = http.createServer(function (request, response) {
response.end("404: Not Found: " + request);
});
var webserver = express();
var httpserver = http.createServer(webserver);

var camera = new (require('./lib/camera'))(config);
var device_service = new (require('./services/device_service.js'))(config, webserver);
var media_service = new (require('./services/media_service.js'))(config, webserver, camera);
var camera = new (require('./lib/camera'))(config, webserver);
var device_service = new (require('./services/device_service.js'))(config, httpserver);
var media_service = new (require('./services/media_service.js'))(config, httpserver, camera);

device_service.start();
media_service.start();
5 changes: 3 additions & 2 deletions services/device_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var util = require("util");
var os = require('os');
var config = require('../config');
var Service = require('../lib/service');
var utils = require('../lib/utils');

var DeviceService = function (config, server) {
Service.apply(this, [config, server]);
Expand Down Expand Up @@ -76,7 +77,7 @@ DeviceService.prototype.extendService = function () {

if (category == "All" || category == "Device") {
GetCapabilitiesResponse.Capabilities.Device = {
XAddr : "http://" + config.IpAddress + ":" + config.ServicePort + "/onvif/device_service",
XAddr : "http://" + (utils.getIpAddress(config.NetworkAdapter) || config.IpAddress) + ":" + config.ServicePort + "/onvif/device_service",
Network : {
IPFilter : false,
ZeroConfiguration : false,
Expand Down Expand Up @@ -134,7 +135,7 @@ DeviceService.prototype.extendService = function () {
}
if (category == "All" || category == "Device") {
GetCapabilitiesResponse.Capabilities.Media = {
XAddr : "http://" + config.IpAddress + ":" + config.ServicePort + "/onvif/media_service",
XAddr : "http://" + (utils.getIpAddress(config.NetworkAdapter) || config.IpAddress) + ":" + config.ServicePort + "/onvif/media_service",
StreamingCapabilities : {
RTPMulticast : false,
RTP_TCP : true,
Expand Down
10 changes: 5 additions & 5 deletions services/media_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ util.inherits(MediaService, Service);
MediaService.prototype.starting = function () {
var listeners = this.webserver.listeners('request').slice();
this.webserver.removeAllListeners('request');
this.webserver.addListener('request', function (request, response) {
this.webserver.addListener('request', function (request, response, next) {
utils.log.debug('web request received : %s', request.url);

var request = url.parse(request.url, true);
var action = request.pathname;
var uri = url.parse(request.url, true);
var action = uri.pathname;
if (action == '/web/snapshot.jpg') {
try {
var img = fs.readFileSync('/dev/shm/snapshot.jpg');
Expand All @@ -44,7 +44,7 @@ MediaService.prototype.starting = function () {
}
} else {
for (var i = 0, len = listeners.length; i < len; i++) {
listeners[i].call(this, request, response);
listeners[i].call(this, request, response, next);
}
}
});
Expand Down Expand Up @@ -183,7 +183,7 @@ MediaService.prototype.extendService = function () {
port.GetStreamUri = function (args /*, cb, headers*/) {
var GetStreamUriResponse = {
MediaUri : {
Uri : "rtsp://" + config.IpAddress + ":" + config.RTSPPort + "/" + config.RTSPName,
Uri : "rtsp://" + (utils.getIpAddress(config.NetworkAdapter) || config.IpAddress) + ":" + config.RTSPPort + "/" + config.RTSPName,
InvalidAfterConnect : false,
InvalidAfterReboot : false,
Timeout : "PT30S"
Expand Down
17 changes: 17 additions & 0 deletions views/camera.ntl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Camera settings</title>
</head>
<body>
<form method="post">
<table>
<tbody>
{{row}}
</tbody>
</table>
<button type="submit">SET</button>
</form>
</body>
</html>

0 comments on commit 55c659e

Please sign in to comment.