doccam-pi/main.js

454 lines
13 KiB
JavaScript
Raw Normal View History

2016-09-07 14:28:52 +12:00
const sock = require('socket.io-client');
const ffmpeg = require('fluent-ffmpeg');
const http = require('http');
const path = require('path');
const fs = require('fs');
const WMStrm = require(__dirname + '/lib/memWrite.js');
const mustBe = false;
const restart = false;
const exec = require('child_process').exec;
const execSync = require('child_process').execSync;
const spawnP = require('child_process').spawn;
const winston = require('winston');
2016-08-31 15:01:06 +12:00
let config, source, snapSource;
2016-09-07 14:45:15 +12:00
2016-09-07 14:28:52 +12:00
const importance = ['', 'info', 'warning', 'danger', 'success'];
2016-08-26 14:35:18 +12:00
2016-08-31 15:01:06 +12:00
let dir = '/home';
2016-08-26 14:35:18 +12:00
let status = {
2016-08-29 17:10:01 +12:00
status: 0,
error: -1
2016-08-26 14:35:18 +12:00
}
2016-08-29 17:10:01 +12:00
let errors = ['Camera Disconnected', 'YoutTube Disconnected', 'Wrong ffmpeg executable.'];
2016-09-01 23:28:19 +12:00
let cmd, sshpid;
2016-08-26 14:35:18 +12:00
2016-09-01 23:28:19 +12:00
let spawn = function() {
2016-08-29 17:10:01 +12:00
source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile;
ffmpeg.setFfmpegPath(config.ffmpegPath)
delete cmd;
cmd = ffmpeg({
source: source,
niceness: -20,
stdoutLines: 20
})
.videoCodec('copy')
.audioBitrate('128k')
.audioChannels(1)
.audioFrequency(11025)
.audioCodec('libmp3lame')
2016-09-01 23:28:19 +12:00
.on('start', function(commandLine) {
2016-08-29 17:10:01 +12:00
status.running = 0;
2016-09-07 14:28:52 +12:00
winston.log(importance[4], 'Spawned Ffmpeg with command: ' + commandLine);
2016-08-29 17:10:01 +12:00
})
2016-09-01 23:28:19 +12:00
.on('end', function(o, e) {
2016-08-29 17:10:01 +12:00
imDead('Normal Stop.', e);
})
2016-09-01 23:28:19 +12:00
.on('error', function(err, o, e) {
2016-08-29 17:10:01 +12:00
console.log(err);
2016-08-30 16:03:13 +12:00
if (err.message.indexOf(source) > -1)
2016-08-29 17:10:01 +12:00
criticalProblem(0, handleDisc, config.camIP, config.camPort)
2016-08-30 16:03:13 +12:00
else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1)
2016-08-29 17:10:01 +12:00
criticalProblem(1, handleDisc, 'a.rtmp.youtube.com/live2/', 1935);
else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1)
2016-09-01 23:28:19 +12:00
criticalProblem(2, function() {});
2016-08-30 15:29:53 +12:00
else if (err.message.indexOf('SIGKILL') > -1)
2016-08-30 15:26:03 +12:00
imDead('Normal Stop.', e);
2016-08-29 17:10:01 +12:00
else
imDead(err.message, e);
})
.outputFormat('flv')
.outputOptions(['-bufsize 50000k', '-tune film'])
.output('rtmp://a.rtmp.youtube.com/live2/' + config.key);
status.error = -1;
2016-08-26 14:35:18 +12:00
socket.emit('change', {
type: 'startStop',
change: {
2016-08-29 17:10:01 +12:00
running: 0,
error: -1
2016-08-26 14:35:18 +12:00
}
});
cmd.run();
}
2016-09-01 23:28:19 +12:00
let getSnap = function(cb) {
2016-08-29 17:10:01 +12:00
snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile;
2016-08-26 14:35:18 +12:00
let picBuff = new WMStrm();
recCmd = ffmpeg(snapSource)
2016-09-01 23:28:19 +12:00
.on('start', function(commandLine) {
2016-09-07 14:28:52 +12:00
winston.log(importance[4], 'Snapshot ' + commandLine);
2016-08-26 14:35:18 +12:00
})
2016-09-01 23:28:19 +12:00
.on('error', function(err, o, e) {})
2016-08-26 14:35:18 +12:00
.outputFormat('mjpeg')
.frames(1)
.stream(picBuff, {
end: true
});
2016-09-01 23:28:19 +12:00
picBuff.on('finish', function() {
2016-08-26 14:35:18 +12:00
try {
cb(picBuff.memStore.toString('base64'));
delete pickBuff;
} catch (e) {
cb(false);
}
});
}
function imDead(why, e = '') {
2016-08-29 17:10:01 +12:00
status.running = 1;
2016-08-26 14:35:18 +12:00
socket.emit('change', {
type: 'startStop',
change: {
2016-08-29 17:10:01 +12:00
running: 1,
2016-08-26 14:35:18 +12:00
}
});
if (restart) {
spawn();
restart = false;
}
if (!mustBe) {
2016-09-07 14:28:52 +12:00
winston.log(importance[2], 'Crash! ' + why + ' ' + e);
2016-09-01 23:28:19 +12:00
setTimeout(function() {
2016-08-26 14:35:18 +12:00
spawn();
}, 1000);
}
mustBe = false;
}
function criticalProblem(err, handler, ...args) {
2016-09-01 23:28:19 +12:00
setTimeout(function() {
2016-08-30 16:03:13 +12:00
status.running = 2
status.error = err
2016-09-07 14:28:52 +12:00
winston.log(importance[3], 'Critical Problem: ' + errors[err]);
2016-08-30 16:03:13 +12:00
socket.emit('change', {
type: 'error',
change: {
running: 2,
error: err
}
});
handler(args)
}, 1000);
2016-08-26 14:35:18 +12:00
}
function handleDisc(info) {
let [host, port] = info
isReachable(host, port, is => {
if (is) {
2016-09-01 23:28:19 +12:00
spawn();
2016-08-26 14:35:18 +12:00
} else {
2016-09-01 23:28:19 +12:00
setTimeout(function() {
2016-08-26 14:35:18 +12:00
handleDisc(info)
}, 10000);
}
});
}
function isReachable(host, port, callback) {
http.get({
2016-08-31 15:01:06 +12:00
host: host.split('/')[0],
2016-08-26 14:35:18 +12:00
port: port
2016-09-01 23:28:19 +12:00
}, function(res) {
2016-08-26 14:35:18 +12:00
callback(true);
2016-09-01 23:28:19 +12:00
}).on("error", function(e) {
2016-08-26 14:35:18 +12:00
if (e.message == "socket hang up")
callback(true)
else
callback(false);
});
}
2016-08-29 17:10:01 +12:00
var commandHandlers = function commandHandlers(command, cb) {
2016-08-26 14:35:18 +12:00
var handlers = {
2016-09-01 23:28:19 +12:00
startStop: function() {
2016-08-29 17:10:01 +12:00
if (status.running !== 2)
if (status.running === 0) {
2016-09-07 14:28:52 +12:00
winston.log(importance[1], "Stop Command!");
2016-08-29 17:10:01 +12:00
mustBe = true
cmd.kill();
2016-09-01 16:10:42 +12:00
socket.emit('data', {
type: 'message',
data: {
title: 'Success',
type: 'success',
text: 'Stopped!'
}
}, command.sender);
2016-08-29 17:10:01 +12:00
} else {
2016-09-07 14:28:52 +12:00
winston.log(importance[1], "Start Command!");
2016-08-29 17:10:01 +12:00
spawn();
2016-09-01 16:10:42 +12:00
socket.emit('data', {
type: 'message',
data: {
title: 'Success',
type: 'success',
text: 'Started!'
}
}, command.sender);
2016-08-29 17:10:01 +12:00
}
2016-08-26 14:35:18 +12:00
},
2016-09-01 23:28:19 +12:00
snap: function() {
2016-08-26 14:35:18 +12:00
getSnap(snap => {
socket.emit('data', {
type: 'snap',
data: snap,
}, command.sender);
});
},
2016-09-01 23:28:19 +12:00
config: function() {
2016-08-29 17:10:01 +12:00
socket.emit('data', {
type: 'config',
data: config,
}, command.sender);
},
2016-09-01 23:28:19 +12:00
changeSettings: function() {
2016-08-29 17:10:01 +12:00
for (let set in command.data) {
if (config[set])
config[set] = command.data[set];
}
let oldConfigured;
2016-08-30 16:03:13 +12:00
if (config.configured === true)
2016-08-29 17:10:01 +12:00
oldConfigured = true;
config.configured = true;
2016-09-06 00:15:52 +12:00
fs.writeFile(__dirname + '/config.js',
JSON.stringify(config,
undefined, 2),
function(err) {
if (err) {
socket.emit('data', {
type: 'message',
data: {
title: 'Error',
type: 'error',
text: 'Can\'t save the Settings!\n' + err.message
2016-08-29 17:10:01 +12:00
}
2016-09-06 00:15:52 +12:00
}, command.sender);
2016-08-29 17:10:01 +12:00
} else {
2016-09-07 14:28:52 +12:00
restartSSH(() => {
socket.emit('data', {
type: 'message',
data: {
title: 'Success',
type: 'success',
text: 'Settings Saved!'
}
}, command.sender);
if (oldConfigured) {
socket.emit('change', {
type: 'settings',
change: {
config: command.data
}
});
cmd.kill();
spawn();
} else {
socket.disconnect();
init();
}
2016-09-06 00:15:52 +12:00
});
2016-08-29 17:10:01 +12:00
}
2016-09-06 00:15:52 +12:00
});
2016-08-29 17:10:01 +12:00
},
2016-09-01 23:28:19 +12:00
restart: function() {
2016-08-29 17:10:01 +12:00
if (status.running === 0) {
2016-09-07 14:28:52 +12:00
winston.log(importance[1], "Restart Command!");
2016-08-26 14:35:18 +12:00
mustBe = true;
restart = true;
cmd.kill();
} else {
2016-09-07 14:28:52 +12:00
winston.log(importance[1], "Start Command!");
2016-08-26 14:35:18 +12:00
spawn();
}
2016-09-01 16:10:42 +12:00
socket.emit('data', {
type: 'message',
data: {
title: 'Success',
type: 'success',
text: 'Restarted!'
}
}, command.sender);
},
restartSSH: function() {
2016-09-06 00:15:52 +12:00
restartSSH(() => {
socket.emit('data', {
type: 'message',
data: {
title: 'Success',
type: 'success',
text: 'Restarted SSH Tunnels!'
}
}, command.sender);
2016-09-01 16:10:42 +12:00
});
2016-08-26 14:35:18 +12:00
},
2016-09-01 23:28:19 +12:00
getLogs: function() {
2016-09-07 14:38:00 +12:00
fs.readFile(__dirname + '/' + config.logPath, 'utf-8', function(err, data) {
2016-08-26 14:35:18 +12:00
if (err) throw err;
2016-09-07 14:28:52 +12:00
let lines = data.trim().split('}\n').slice(-100);
2016-08-26 14:35:18 +12:00
lines.shift();
lines.pop();
socket.emit('data', {
type: 'logs',
data: lines
}, command.sender);
});
}
}
//call the handler
var call = handlers[command.command];
if (call)
call();
}
2016-09-06 00:15:52 +12:00
function restartSSH(cb) {
exec('forever stop SSH-Serv', () => {
connectSSH(() => {
socket.emit('change', {
change: {
ssh: {
port: status.ssh.port,
camForwardPort: status.ssh.camForwardPort
}
}
});
cb();
});
2016-08-26 14:35:18 +12:00
});
}
function handleKill() {
process.stdin.resume();
2016-09-07 14:28:52 +12:00
winston.log(importance[0], "Received Shutdown Command");
2016-09-01 16:39:49 +12:00
mustBe = true;
cmd.kill();
2016-09-01 16:35:01 +12:00
process.exit(0);
2016-08-26 14:35:18 +12:00
}
2016-09-01 23:28:19 +12:00
process.on('SIGTERM', function() {
2016-08-26 14:35:18 +12:00
handleKill();
});
//let's go
2016-08-29 17:10:01 +12:00
function init() {
2016-09-04 05:05:43 +00:00
config = readConfig();
2016-09-07 14:45:15 +12:00
winston.add(winston.transports.File, {
filename: __dirname + '/' + config.logPath,
maxsize: 2048,
maxFiles: 10
});
2016-08-29 17:10:01 +12:00
if (config.configured) {
socket = sock(config.master + '/pi');
initSocket();
status.name = config.name;
2016-08-31 15:01:06 +12:00
if (!cmd)
spawn();
2016-08-29 17:10:01 +12:00
} else {
socket = sock(config.master + '/pi', {
query: "unconfigured=true"
});
status.running = 2;
initSocket();
}
}
2016-08-31 15:01:06 +12:00
2016-09-01 23:28:19 +12:00
let d = false;
2016-08-31 15:01:06 +12:00
2016-09-01 23:28:19 +12:00
function initSSH(cb) {
status.ssh = {
user: config['ssh-user'],
localUser: config['ssh-local-user'],
masterPort: config['ssh-port']
};
2016-08-31 15:01:06 +12:00
2016-09-01 15:39:33 +12:00
checkSSH((alive) => {
if (alive)
cb();
else
connectSSH(cb);
2016-09-01 23:28:19 +12:00
});
}
2016-08-31 15:01:06 +12:00
2016-09-01 23:28:19 +12:00
function connectSSH(cb = function() {
socket.emit('change', {
change: {
ssh: {
port: status.ssh.port,
camForwardPort: status.ssh.camForwardPort
}
}
});
}) {
socket.emit('getSSHPort', ports => {
[status.ssh.port, status.ssh.camForwardPort] = ports;
2016-09-07 14:28:52 +12:00
let ssh = exec(`forever start -a --killSignal=SIGINT --uid SSH-Serv sshManager.js ${status.ssh.port} ${status.ssh.camForwardPort} ${config.camPanelPort}`, {
2016-09-01 15:39:33 +12:00
detached: true,
2016-09-04 05:05:43 +00:00
shell: true,
2016-09-06 00:15:52 +12:00
cwd: __dirname
2016-09-01 23:28:19 +12:00
});
2016-09-01 23:40:49 +12:00
ssh.on('error', (err) => {
throw err;
2016-09-01 23:28:19 +12:00
});
cb();
});
}
2016-08-31 15:01:06 +12:00
2016-09-01 23:28:19 +12:00
function checkSSH(cb) {
2016-09-01 15:39:33 +12:00
let m, pid, alive;
let re = /SSH-Serv.*?sshManager\.js\s([0-9]+)\s([0-9]+).*log.*[^STOPPED]+/g;
exec('forever list', (error, stdout, stderr) => {
if (error)
throw error;
let alive = false;
2016-09-01 23:28:19 +12:00
while ((m = re.exec(stdout)) !== null) {
if (m.index === re.lastIndex) {
re.lastIndex++;
2016-08-31 15:01:06 +12:00
}
2016-09-01 15:39:33 +12:00
if (alive) {
2016-09-01 16:10:42 +12:00
exec('forever stop SSH-Serv');
2016-09-01 15:39:33 +12:00
cb(false)
return;
} else {
[, status.ssh.port, status.ssh.camForwardPort] = m;
alive = true;
}
2016-09-01 23:28:19 +12:00
}
2016-09-01 15:39:33 +12:00
cb(alive);
2016-09-01 23:28:19 +12:00
});
}
function initSocket() {
socket.on('connect', function() {
2016-09-07 14:28:52 +12:00
winston.log(importance[0], 'Connected to Master: ' + config.master + '.');
2016-09-01 23:28:19 +12:00
if (config['ssh-user'])
initSSH(err => {
if (err)
throw err;
socket.emit('meta', status);
});
else {
2016-08-31 15:01:06 +12:00
socket.emit('meta', status);
2016-09-01 23:28:19 +12:00
}
2016-08-29 17:10:01 +12:00
});
2016-09-01 23:28:19 +12:00
socket.on('disconnect', function() {
d = true;
2016-08-29 17:10:01 +12:00
socket.disconnect();
init();
});
socket.on('command', (command, cb) => {
commandHandlers(command, cb);
});
}
function readConfig() {
2016-09-04 05:05:43 +00:00
return JSON.parse(fs.readFileSync(__dirname + '/config.js'));
2016-08-29 17:10:01 +12:00
}
2016-09-01 23:28:19 +12:00
init();