Commit 10412f81 authored by Andrey Filippov's avatar Andrey Filippov

working on LWIR16 control

parent 2c148fb3
......@@ -26,7 +26,7 @@ var apply_pending = false;
function parse_response(resp){
var result = "";
console.log(resp);
console.log("parse_response(), resp="+resp);
if (resp.getElementsByTagName("result").length!=0){
result = resp.getElementsByTagName("result")[0].childNodes[0].nodeValue;
}else if (resp.getElementsByTagName("reboot").length!=0){
......@@ -40,11 +40,13 @@ function parse_response(resp){
}
function send_request(rq,callback){
console.log("send_request(), rq="+rq);
var request = new XMLHttpRequest();
request.open('GET', rq, true);
request.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var resp = this.responseXML;
console.log("send_request(), callback, resp="+resp);
callback(resp);
}
};
......@@ -55,8 +57,10 @@ function send_request(rq,callback){
}
function sendStatusRequest(){
console.log("sendStatusRequest()");
var url = "http://192.168.0.41/lwir16/lwir16.php?daemon=status";
if (apply_pending) {
console.log("sendStatusRequest(), apply_pending");
var mod_pars = modParameters();
// console.log('mod_pars members:');
for(const p in mod_pars) {
......@@ -69,6 +73,7 @@ function sendStatusRequest(){
}
function sendRestartRequest(){
console.log("sendRestartRequest()");
var url = "http://192.168.0.41/lwir16/lwir16.php?daemon=restart&cmd=STATUS";
send_request(url,parseStatusResponse);
}
......@@ -76,6 +81,7 @@ function sendRestartRequest(){
function parseStatusResponse(resp){
console.log("parseStatusResponse(), resp="+resp);
var result = "";
// console.log(resp);
if (update_editable) {
......@@ -158,6 +164,7 @@ function parseStatusResponse(resp){
request_num++;
document.getElementById("idrequest_num").value = request_num;
update_editable = false;
console.log("parseStatusResponse() almost Done, sending again sendStatusRequest()");
sendStatusRequest();
}
......
#configuration for LWIR 16 camera
multicam_dir = "footage01"
multicam_rperiod = 5.0
multicam_speriod = 2.0
#save current settings to overwrite those in this file
multicam_conf = "/etc/elphel393/multicamconf.xml"
ips = "192.168.0.41,192.168.0.42,192.168.0.43,192.168.0.44,192.168.0.45"
port_masks= "15,15,15,15,15"
duration = 100
......
......@@ -487,7 +487,7 @@ EOT;
return $results;
}
function runCapture($run_ffc, $nowait = 0, $debug=0) {
function runCapture($run_ffc, $nowait = 0, $debug=0) { // works, but the main script hangs
// TODO: use lwir16.ini
// $eo_quality = 97;
// $exposure = 1000; // 1 ms
......@@ -662,7 +662,7 @@ EOT;
$GLOBALS[CAPTURE_RUN] = 0; // until command
$GLOBALS[LAST_FFC] = 0; // overdue
$GLOBALS[SECUENCE_NUM] = 0;
if ($GLOBALS[DEBUG > 1]) {
if ($GLOBALS[DEBUG] > 1) { // was if ($GLOBALS[DEBUG > 1]) {
printf("--- GLOBALS: ---\n");
print_r($GLOBALS);
}
......
......@@ -11,7 +11,8 @@ DOCS= index.html \
multicam.css \
multicam2.css \
multicam.php \
multicam2.php
multicam2.php \
reboot.html
ZIPDOCS= zip/deflate.js \
zip/inflate.js \
......
......@@ -25,7 +25,90 @@
<div id='display_controls'>
<button type="button" class="collapsible">Recording Status</button>
<div id='display_status'></div>
<button type="button" class="collapsible">LWIR16 Status</button>
<div id='lwir16_status'>
<table><tr><td>
<table>
<tr><td><label for="seq_num" >Seq Num</label></td><td><input type="text" id="idsequence_num" name="seq_num" size="5" value="?" disabled="disabled"></td></tr>
<tr><td><label for="request_num">Requests</label></td><td><input type="text" id="idrequest_num" name="request_num" size="5" value="?" disabled="disabled"></td></tr>
<tr><td><label for="capture_run">Running</label></td><td><input type="checkbox" id="idcapture_run" name="capture_run" disabled="disabled"></td></tr>
</table>
</td><td>
<table>
<tr><td><label for="last_ffc" >Last FFC</label></td><td><input type="text" id="idlast_ffc" name="last_ffc" size="5" value="?" disabled="disabled">s</td></tr>
<tr><td><label for="time_to_ffc" >To FFC</label></td><td><input type="text" id="idtime_to_ffc" name="time_to_ffc" size="5" value="?" disabled="disabled">s</td></tr>
<tr><td colspan = "2">&nbsp;</td></tr>
</table>
</td></tr></table>
</div>
<button type="button" class="collapsible">LWIR16 Controls</button>
<div id='lwir16_controls'>
<table><tr><td>
<table>
<tr><td><button id="idStartStop" name = "nStartStop" disabled="true">???</button></td>
<td><button id="idApply" name = "nApply" disabled = "true">???</button></td>
</tr>
<tr><td><label for="idpre_delay" >Inter-Delay</label></td><td><input type="text" id="idpre_delay" name="pre_delay" size="5" value="?">s</td></tr>
<tr><td><label for="duration" >LWIR frames</label></td><td><input type="text" id="idduration" name="duration" size="5" value="?"></td></tr>
<tr><td><label for="duration_eo" >RGB frames</label></td><td><input type="text" id="idduration_eo" name="duration_eo" size="5" value="?"></td></tr>
<tr><td><label for="compressor_run">Compress Init</label></td><td><input type="checkbox" id="idcompressor_run" name="compressor_run"></td></tr>
</table>
</td><td>
<table>
<tr><td><label for="ffc" >FFC</label></td><td><input type="checkbox" id="idffc" name="ffc"></td></tr>
<tr><td><label for="ffc_period" >FFC period</label></td><td><input type="text" id="idffc_period" name="ffc_period" size="5" value="?"></td></tr>
<tr><td><label for="ffc_groups" >FFC groups</label></td><td><select id="idffc_groups" name="idffc_groups">
<option value="1">1</option>
<option value="2">2</option>
<option value="4" selected="selected">4</option>
</select>
</td></tr>
<tr><td><label for="ffc_frames" >FFC frames</label></td><td><input type="text" id="idffc_frames" name="ffc_frames" size="5" value="?"></td></tr>
<tr><td><label for="debug" >Debug</label></td><td><input type="text" id="iddebug" name="debug" size="5" value="?"></td></tr>
</table>
</td></tr></table>
</div>
<button type="button" class="collapsible">Multicam Controls</button>
<div id='multicam settings'>
<table>
<tr>
<td>
<table>
<th><td colspan="2">Image Refresh<td> </th>
<tr> <td><label>Enable</label></td>
<td>
<div id="toggle_refresh" class="btn-group btn-toggle">
<button class="btn btn-xs btn-success active">ON</button>
<button class="btn btn-xs btn-default">OFF</button>
</div>
</td> </tr>
<tr><td><label for="ref_rate">Images</label></td>
<td><input type="text" id="ref_rate" name="ref_rate" size="4">s</td>
</tr><tr>
<td><label for="sref_rate">Status</label></td>
<td><input type="text" id="sref_rate" name="sref_rate" size="4">s
</td> </tr>
</table>
</td>
<td>
<table><th><td colspan="2">SSD Recording<td> </th>
<tr><td><label>Record</label></td>
<td><div id="toggle_record" class="btn-group btn-toggle">
<button class="btn btn-xs btn-success active">ON</button>
<button class="btn btn-xs btn-default">OFF</button>
</div>
</td>
</tr><tr> <td><label id = "root_dir" for="ssd_directory">/mnt/sda1/</label></td>
<td><input type="text" id="dir_name" name="ssd_directory" size="10"></td>
</tr><tr><td colspan="2">&nbsp;</td></tr></table>
</td>
</tr>
</table>
</div>
<button type="button" class="collapsible">Eyesis Controls</button>
<div id='settings'>
<table>
......@@ -52,11 +135,22 @@
<td>Auto Exposure:</td>
<td>
<div id="toggle_aexp" class="btn-group btn-toggle">
<button class="btn btn-xs btn-success active">ON</button>
<button class="btn btn-xs btn-default">OFF</button>
</div>
</td>
<button class="btn btn-xs btn-success active">ON</button>
<button class="btn btn-xs btn-default">OFF</button>
</div>
</td>
</tr>
<tr id="manual_exposure_div" title='Manual Exposure'>
<td><label for="exposure_ms">Manual</label></td>
<td><input type="text" id="exposure_ms" name="exposure_ms" size="6" value="50">ms</td>
</tr>
<tr id="image_quality_div" title='Image Quality'>
<td><label for="jpeg_quality">Quality</label></td>
<td><input type="text" id="jpeg_quality" name="jpeg_quality" size="6" value="97">%</td>
</tr>
<tr>
<td>
<button id='system_tests' title='system info' class="btn btn-sm btn-success">tests</button>
......
......@@ -7,7 +7,8 @@ var Port = function(options){
index: 0,
awb: NaN,
aexp: NaN,
preview: null
preview: null,
sensor_type:'none'
};
this._data = $.extend(defaults,options);
......@@ -17,6 +18,7 @@ var Port = function(options){
this.awb = this._data.awb;
this.aexp = this._data.aexp;
this.preview = this._data.preview;
this.sensor_type = this._data.sensor_type;
}
......@@ -29,6 +31,7 @@ var Camera = function(options){
status: false,
camogm: false,
recording: false,
mounted: false,
ports: []
};
......@@ -38,77 +41,58 @@ var Camera = function(options){
this.init = this._data.init;
this.status = this._data.status;
this.ports = this._data.ports;
};
// global
var recording = false;
var ips_from_url = false;
//var ips_from_url = false;
var cams = [];
var wb_en = 1;
var aexp_en = 1;
var skip_previews = 0;
var refresh_en = 1;
var record_en = 0;
var multicam_dir = "";
var multicam_rperiod = 5.0; // image refresh period
var multicam_speriod = 2.0; // status refresh period
var use_file_system = 1; // no fast recording
var rec_running_intvl;
var refresh_status_intvl;
var refresh_previews_intvl;
var exp_ms = 50;
var quality = 97;
$(function(){
init();
});
function parseURL(){
var ips_str = location.host;
var parameters=location.href.replace(/\?/ig,"&").split("&");
for (var i=0;i<parameters.length;i++) parameters[i]=parameters[i].split("=");
for (var i=1;i<parameters.length;i++) {
// alert("parseURL(): parameter: "+parameters[i][0]+"="+parameters[i][1]);
// console.log("parseURL(): parameter: "+parameters[i][0]+"="+parameters[i][1]);
switch (parameters[i][0]) {
case "ip":
//ips_from_url = true;
ips_str = parameters[i][1];
ips_str = ips_str.replace(/,|;/gm,'\n');
break;
case "skip_previews": // do not show previews - terribly slow for Bosons
skip_previews = 1;
// alert("parseURL(): got skipping previews");
break;
}
}
// force url
addrs_str2ips(ips_str);
ips_from_url = true;
}
// launch reading configs from the master camera
function init(){
console.log('--PRE parseURL--');
// what can be done before configuring cameras (ip or ajax) and ports (init2)
$.ajax({
url: "multicam2.php?cmd=configs",
success: function(response){
var multicam_dir_xml = $(response).find("multicam_dir");
if (multicam_dir_xml) multicam_dir = multicam_dir_xml.text();
var multicam_rperiod_xml = $(response).find("multicam_rperiod");
if (multicam_rperiod_xml) multicam_rperiod = parseFloat(multicam_rperiod_xml.text());
var multicam_speriod_xml = $(response).find("multicam_speriod");
if (multicam_speriod_xml) multicam_speriod = parseFloat(multicam_speriod_xml.text());
console.log('Got configs from master camera config, multicam_dir='+multicam_dir+
", multicam_rperiod="+multicam_rperiod + ", multicam_speriod="+multicam_speriod);
init1(); // should be done only after request responce!
}
});
}
zip.workerScriptsPath = "../js/zip/";
parseURL();
if (!ips_from_url){
//get config
$.ajax({
url: "multicam2.php?cmd=read",
success: function(data){
var addrs = $(data).find('camera');
for(var i=0;i<addrs.length;i++){
cams.push(new Camera({ip:$(addrs[i]).text()}));
}
init2();
}
});
}else{
init2();
}
function init1(){
// what can be done before configuring cameras (ip or ajax) and ports (init2)
init_rec_button();
//
$("#ea_btn").on('click',function(){
$("#edit_addrs_input").css({
top: $(this).offset().top,
......@@ -121,35 +105,26 @@ function init(){
addrs_ta2ips();
$("#edit_addrs_input").hide();
});
/*
$("#display").css({
position:'absolute',
top: '2px',
left: '2px' // $("#settings").find("table").width()+10
});
*/
/*
$("#display_previews").css({
position:'absolute',
top: '2px',
left: '2px' // $("#settings").find("table").width()+10
});
*/
// alert($("#display_previews").find("table").width());
/*
$("#display_status").css({
position:'absolute',
top: '2px',
left: $("#display_previews").find("table").width()+10
});
*/
zip.workerScriptsPath = "../js/zip/";
console.log('--PRE parseURL--');
init_test_button();
init_snapshot_button();
initCollapsibleElements();
// what can NOT be done before configuring cameras (ip or ajax) and ports (init2)
//get config
$.ajax({
url: "multicam2.php?cmd=ips",
success: function(response){
var ips = $(response).find("ip");
ips.each(function(){
console.log('index='+($(this).attr("index"))+", text="+$(this).text());
cams[$(this).attr("index")] = new Camera ({ip:$(this).text()});
});
console.log('Got IPs from master camera config, ips.length='+ips.length);
init2(); // should be done only after request responce!
}
});
}
function init2(){
......@@ -161,12 +136,17 @@ function init2(){
init_awb_toggle();
init_aexp_toggle();
init_multicam_controls(); // 2022
console.log("Got cameras");
console.log(cams);
}
function get_ports(){
for(var i=0;i<cams.length;i++){
console.log("get_ports():"+i);
$.ajax({
url: "http://"+cams[i].ip+"/multicam/multicam2.php?cmd=ports",
ip:cams[i].ip,
......@@ -181,12 +161,11 @@ function get_ports(){
// ports are already ordered in response
ports.each(function(){
if ($(this).text()!=='none'){
var tmp_port = new Port({port: $(this).attr("port"), index: $(this).attr("index")});
var tmp_port = new Port({port: $(this).attr("port"), index: $(this).attr("index"), sensor_type: $(this).html()});
cams[index].ports.push(tmp_port);
init_port(index,cams[index].ports.length-1);
}
});
// check camogm is alive
check_camogm(index);
......@@ -221,7 +200,8 @@ function check_camogm(cam_i){
}
if (state=='on'){
console.log(cams[this.cam_i].ip+": camogm is on");
check_camogm_status(this.cam_i);
// check_camogm_status(this.cam_i);
setDirSingle(this.cam_i); // wiil end with check_camogm_status(this.cam_i)
}
}
});
......@@ -237,56 +217,111 @@ function camogm_launch(cam_i){
success: function(res){
console.log(cams[this.cam_i].ip+": "+res);
cams[this.cam_i].camogm = 'on';
check_camogm_status(this.cam_i);
// check_camogm_status(this.cam_i);
setDirSingle(this.cam_i); // wiil end with check_camogm_status(this.cam_i)
}
});
}
function check_camogm_status(cam_i){
function check_camogm_status(cam_i){ // full check, slow
if (use_file_system) {
// console.log("check_camogm_status("+cam_i+")1, use_file_system="+use_file_system);
$.ajax({
url: "http://"+cams[cam_i].ip+"/camogm_interface.php?cmd=state", // shorter version, just state
cam_i: cam_i,
success: function(res){
var cam = cams[this.cam_i];
// console.log("check_camogm_status for "+cam_i+" success");
if ($(res).find('state').length!=0){
var state = $(res).find('state').text();
state = state.replace(/"/gm,'');
// false(if not init) or 'on' or 'off'
cams[this.cam_i].recording = state;
rec_button_update_state();
var se = $("#display_status").find("tr[ip='"+cam.ip+"']");
check_camogm_free_space(this.cam_i);
}
}
}).fail(function(data,status){
console.log("check_camogm_status()use_file_system: status request failed)."); //CORS
console.log(data); // CORS
console.log(status);
console.log(cam_i);
});
} else { // raw file system
console.log("check_camogm_status("+cam_i+")2, use_file_system="+use_file_system);
$.ajax({
url: "http://"+cams[cam_i].ip+"/camogm_interface.php?cmd=status",
cam_i: cam_i,
success: function(res){
var cam = cams[this.cam_i];
// console.log("check_camogm_status for "+cam_i+" success");
if ($(res).find('state').length!=0){
var state = $(res).find('state').text();
state = state.replace(/"/gm,'');
// false(if not init) or 'on' or 'off'
cams[this.cam_i].recording = state;
rec_button_update_state();
var se = $("#display_status").find("tr[ip='"+cam.ip+"']");
// device
var device = $(res).find('raw_device_path').text().replace(/"/gm,'');
se.find("#s_device").html(device);
// free space
var lba_end = parseInt($(res).find('lba_end').text());
var lba_current = parseInt($(res).find('lba_current').text());
var free_space = (lba_end - lba_current)/2/1024/1024;
free_space = Math.round(100*free_space)/100;
se.find("#s_space").html(free_space+" GB");
}
}
}).fail(function(data,status){
console.log("status request failed).");
console.log(data);
console.log(status);
console.log(cam_i);
});
}
}
// run_status does not interact with camogm, quickest response
function check_camogm_free_space(cam_i){ // file system, no raw
$.ajax({
url: "http://"+cams[cam_i].ip+"/camogm_interface.php?cmd=status",
url: "http://"+cams[cam_i].ip+"/camogm_interface.php?cmd=get_hdd_space",
cam_i: cam_i,
success: function(res){
var cam = cams[this.cam_i];
if ($(res).find('state').length!=0){
var state = $(res).find('state').text();
state = state.replace(/"/gm,'');
// false(if not init) or 'on' or 'off'
cams[this.cam_i].recording = state;
rec_button_update_state();
var se = $("#display_status").find("tr[ip='"+cam.ip+"']");
// device
var device = $(res).find('raw_device_path').text().replace(/"/gm,'');
se.find("#s_device").html(device);
// free space
var lba_end = parseInt($(res).find('lba_end').text());
var lba_current = parseInt($(res).find('lba_current').text());
var free_space = (lba_end - lba_current)/2/1024/1024;
free_space = Math.round(100*free_space)/100;
se.find("#s_space").html(free_space+" GB");
// console.log("check_camogm_free_space for "+cam_i+" success");
if ($(res).find('get_hdd_space').length!=0){
var free_space = parseInt($(res).find('get_hdd_space').text());
var sdd_found = free_space > 0;
cam.mounted = sdd_found;
free_space /= 1024*1024*1024;
free_space = Math.round(100*free_space)/100;
var se = $("#display_status").find("tr[ip='"+cam.ip+"']");
// device
se.find("#s_device").html(multicam_dir);
// free space
se.find("#s_space").html(free_space+" GB");
var bgcol = sdd_found ? "rgb(200, 255, 200)":"rgb(255, 100,100)"; // add yellow for low space
se.find("#s_space").attr("style","background-color:"+bgcol+";");
}
}
}).fail(function(data,status){
console.log("status request failed).");
console.log("check_camogm_free_space("+cam_i+")status request failed).");
console.log(data);
console.log(status);
console.log(cam_i);
});
}
var all_ready_init_run = true;
function rec_button_update_state(){
......@@ -313,7 +348,7 @@ function rec_button_update_state(){
if (all_ready){
if (any_running && any_stopped){
console.log("WARNING: some camogms are running, some are stopped");
console.log("WARNING: some camogms are running, some are stopped"); // false
}
if (all_ready_init_run){
......@@ -324,20 +359,54 @@ function rec_button_update_state(){
recording = true;
rec_button_switch(recording);
}
/*
if (!skip_previews) { // do not schedule previews as they are very slow for Bosons
refresh_previews_intvl = setInterval(refresh_previews,2000);
refresh_previews_intvl = setInterval(refresh_previews,multicam_rperiod * 1000);
} else {
// alert("skipping previews");
}
refresh_status_intvl = setInterval(refresh_status,2000);
refresh_status_intvl = setInterval(refresh_status,multicam_speriod * 1000);
*/
startStopRefresh();
}
all_ready_init_run = false;
}
}
function startStopRefresh (){
if (refresh_en) {
if (!skip_previews) {
if (refresh_previews_intvl) {
clearInterval(refresh_previews_intvl);
refresh_previews_intvl = null;
}
refresh_previews_intvl = setInterval(refresh_previews,multicam_rperiod * 1000);
refresh_previews(); // first time - immediately
}
if (refresh_status_intvl) {
clearInterval(refresh_status_intvl);
refresh_status_intvl = null;
}
refresh_status_intvl = setInterval(refresh_status,multicam_speriod * 1000);
refresh_status(); // first time - immediately
console.log("setInterval(refresh_previews_intvl); refresh_previews_intvl="+refresh_previews_intvl+', period='+(multicam_rperiod * 1000));
console.log("setInterval(refresh_status_intvl); refresh_status_intvl="+refresh_status_intvl+', period='+(multicam_speriod * 1000));
} else {
clearInterval(refresh_previews_intvl);
clearInterval(refresh_status_intvl);
console.log("clearInterval(refresh_previews_intvl); refresh_previews_intvl="+refresh_previews_intvl);
console.log("clearInterval(refresh_status_intvl); refresh_status_intvl="+refresh_status_intvl);
refresh_previews_intvl = null;
refresh_status_intvl = null;
blankBackground($('#display_previews'));
blankBackground($('#display_status'));
}
console.log("startStopRefresh(): "+refresh_en)
}
//var refresh_status_intvl;
//var refresh_previews_intvl;
......@@ -424,9 +493,7 @@ function button_update_state(btn){
}else{
start = true;
}
old_port_attr = port_attr;
}
if (!all_ready){
break;
......@@ -506,9 +573,14 @@ function init_test_button(){
}
function refresh_previews(){
// console.log("refresh_previews(), refresh_en = "+refresh_en);
if (!refresh_en){
return;
}
blinkBackground($('#display_previews'));
// $('#display_previews').css('background-color', 'rgb(255,220,220)');
// setTimeout( function(){ $('#display_previews').css('background-color', 'rgb(230,230,230)');}, 300);
var ts = Date.now();
for(var i=0;i<cams.length;i++){
if (cams[i].init){
for(var j=0;j<cams[i].ports.length;j++){
......@@ -525,7 +597,7 @@ function refresh_previews(){
var jp4prev = elem.find(".port_preview[index="+j+"]");
var imgsrv_img = "/img";
if (is_lwir){
imgsrv_img = "/tiff_palette=2/tiff_telem=1/tiff_auto=33/tiff_convert/bimg";
imgsrv_img = "/tiff_palette=2/tiff_telem=1/tiff_auto=33/tiff_convert/img"; // /bimg";
}
var preview = jp4prev.jp4({
//ip: cam.ip,
......@@ -555,7 +627,23 @@ function refresh_previews(){
}
function blinkBackground(elem) {
$(elem).css('background-color', 'rgb(255,230,230)');
setTimeout( function(){ $(elem).css('background-color', 'rgb(240,240,240)');}, 500);
}
function blankBackground(elem) {
$(elem).css('background-color', 'rgb(255,255,255)');
setTimeout( function(){ $(elem).css('background-color', 'transparent');}, 600); // little longer than blink
}
function refresh_status(){
// console.log("---------------------refresh_status(), refresh_en = "+refresh_en);
if (!refresh_en){
return;
}
blinkBackground($('#display_status'));
for(var i=0;i<cams.length;i++){
if (cams[i].init){
check_camogm_status(i);
......@@ -566,21 +654,47 @@ function refresh_status(){
function init_rec_button(){
$("#rec_button").on('click',function(){
recording = !recording;
rec_button_switch(recording);
if (recording){
url = "camogm_interface.php?cmd=start";
}else{
// recording = !recording;
// rec_button_switch(recording);
if (recording){ // simple, always works
url = "camogm_interface.php?cmd=stop";
}
multi_ajax(url,function(res){
console.log(this.ip+": rec = "+recording);
});
//http://{$unique_cams[$i]['ip']}/camogm_interface.php?cmd=start
multi_ajax(url,function(res){
console.log(this.ip+": rec = "+recording);
});
recording = 0;
rec_button_switch(recording);
return;
}else{ // wants to turn on recording, needs tests
// check that all cameras have mounted partitions
for(var i=0;i<cams.length;i++){
if (!cams[i].mounted){
console.log("Camera "+i+" is not mounted, can not start recording");
return; // do nothing, button will not activate
}
}
recording = 1;
rec_button_switch(recording);
// mkdir for recording
url = "camogm_interface.php?cmd=dir_prefix&name=" + multicam_dir;
multi_ajax(url,function(){
console.log("starting recording in the camera "+ this.ip+" to " + multicam_dir);
// launch recording
$.ajax({
url: "http://"+this.ip+"/"+"camogm_interface.php?cmd=start",
ip: this.ip,
success: function(res){
console.log(this.ip+": rec = "+recording); // got false on 41?
// recording = 1;
// rec_button_switch(recording);
} // multi_ajax function
}); // ajax
}); //multi_ajax
} // if (recording) else
}); // on.("click")
} // init_rec_button
});
}
function rec_button_switch(state){
......@@ -596,9 +710,6 @@ function rec_button_switch(state){
}
var rec_running_intvl;
var refresh_status_intvl;
var refresh_previews_intvl;
function rec_running(state){
if (state){
......@@ -715,13 +826,11 @@ function addrs_mark_bad_ip(ip){
function init_awb_toggle(){
$('#toggle_awb').click(function() {
if ($(this).find('.btn.active').html()=="ON"){
wb_en = 0;
}else{
wb_en = 1;
}
button_switch($(this),wb_en);
// will it work without port 0?
......@@ -729,16 +838,13 @@ function init_awb_toggle(){
multi_ajax(url,function(res){
console.log(this.ip+": awb "+wb_en);
});
}, 'mt9p006');
});
}
// on or off
function button_switch(btn,state){
//btn = $('#toggle_awb');
if (state==1){
if (btn.find('.btn.active').html()=="OFF"){
btn.find('.btn.active').toggleClass('btn-danger');
......@@ -763,41 +869,68 @@ function button_switch(btn,state){
function init_aexp_toggle(){
$('#toggle_aexp').click(function() {
console.log("toggle_aexp(): aexp "+aexp_en);
if ($(this).find('.btn.active').html()=="ON"){
aexp_en = 0;
}else{
aexp_en = 1;
}
button_switch($(this),aexp_en); // just repeats aexp_en
updateAexpExpQuality();
});
// init onchange quality, exposure:
button_switch($(this),aexp_en);
$('#exposure_ms').change(function() {
updateAexpExpQuality();
console.log("change exposure_ms " + exposure_ms);
});
console.log("init change exposure_ms");
$('#jpeg_quality').change(function() {
updateAexpExpQuality();
console.log("change jpeg_quality: " + quality);
});
console.log("init change jpeg_quality");
}
function updateAexpExpQuality(){
// button_switch($(this),aexp_en); // just repeats aexp_en
// will it work without port 0?
url = "parsedit.php?immediate&sensor_port=0&AUTOEXP_ON="+aexp_en+"&*AUTOEXP_ON=0xf";
// get exposure from field
exp_ms = $('#exposure_ms').val();
var exp_us = Math.round(1000*exp_ms);
quality = Math.round($('#jpeg_quality').val());
var exp_us = Math.round(1000*exp_ms);
url = "parsedit.php?immediate&sensor_port=0&AUTOEXP_ON="+aexp_en+"&*AUTOEXP_ON=0xf&EXPOS="+exp_us+"&*EXPOS=0xf&QUALITY="+quality+"&*QUALITY=0xf";
console.log("updateAexpExpQuality(): url="+url);
multi_ajax(url,function(res){
console.log(this.ip+": aexp "+aexp_en);
});
});
}, 'mt9p006');
}
function multi_ajax(url,callback){
// TODO: Split to send exposure on change and quality
function multi_ajax(url,callback,sensor_type=""){
console.log ("multi_ajax(): url="+url+", sensor_type="+sensor_type);
for(var i=0;i<cams.length;i++){
if (cams[i].status){
$.ajax({
url: "http://"+cams[i].ip+"/"+url,
ip: cams[i].ip,
success: callback
});
if ((sensor_type == "") || (sensor_type == cams[i].ports[0].sensor_type)) {
$.ajax({
url: "http://"+cams[i].ip+"/"+url,
ip: cams[i].ip,
success: callback
});
}
}
}
}
var pointers = [];
function init_snapshot_button(){
......@@ -813,7 +946,7 @@ function init_snapshot_button(){
//find the latest
snapshot_download_all(pointers);
}
});
}, 'mt9p006');
// download images
});
......@@ -1105,23 +1238,38 @@ function pass_to_file_reader(filename,fileblob){
}
//console.log(cams[this.cam_i].ip+": camogm is on");
var collapsible_elements;
function initCollapsibleElements() {
collapsible_elements = document.getElementsByClassName("collapsible");
// console.log("There are "+collapsible_elements.length+" collapsible elements");
function initCollapsibleElements_OLD() {
// console.log("+++++ $('.collapsible').length="+$('.collapsible').length);
// collapsible_elements = document.getElementsByClassName("collapsible");
collapsible_elements = $('.collapsible');
console.log("+++++ collapsible_elements.length="+collapsible_elements.length);
var i;
for (i = 0; i < collapsible_elements.length; i++) {
collapsible_elements[i].addEventListener("click", function() {
// console.log("collapsible clicked:"+this.outerHTML);
this.classList.toggle("active");
var content = this.nextElementSibling;
// console.log("content:"+content.outerHTML);
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
// console.log("collapsible processed:"+this.outerHTML);
// console.log("content:"+content.outerHTML);
});
}
}
function initCollapsibleElements() {
collapsible_elements = $('.collapsible');
console.log("+++++ collapsible_elements.length="+collapsible_elements.length);
var i;
for (i = 0; i < collapsible_elements.length; i++) {
$(collapsible_elements[i]).click(function(){
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "none") {
content.style.display = "block";
} else {
content.style.display = "none";
}
});
}
}
......@@ -1129,8 +1277,205 @@ function initCollapsibleElements() {
function init_multicam_controls(){
refresh_en = 1;
button_switch($('#toggle_refresh'),refresh_en);
startStopRefresh();
record_en = 0;
button_switch($('#toggle_record'),record_en);
// set input values
$('#dir_name').val(multicam_dir);
$('#ref_rate').val(multicam_rperiod);
$('#sref_rate').val(multicam_speriod);
$('#toggle_refresh').click(function() {
if ($(this).find('.btn.active').html()=="ON"){
refresh_en = 0;
}else{
refresh_en = 1;
}
button_switch($(this),refresh_en);
startStopRefresh();
// Add actual functionality
});
$('#toggle_record').click(function() {
if ($(this).find('.btn.active').html()=="ON"){
record_en = 0;
}else{
record_en = 1;
}
button_switch($(this),record_en);
// Add actual functionality
});
$('#dir_name').change(function() {
multicam_dir = $(this).val();
console.log("dir_name="+multicam_dir );
updateConfifs();
setDirMulti();
});
$('#ref_rate').change(function() {
multicam_rperiod = parseFloat($(this).val());
console.log("ref_rate="+multicam_rperiod );
updateConfifs();
});
$('#sref_rate').change(function() {
multicam_speriod = parseFloat($(this).val());
console.log("sref_rate="+multicam_speriod );
updateConfifs();
});
console.log('init_multicam_controls() DONE');
}
function updateConfifs(){ // write modified settings to the master camera persistent storage
// what can be done before configuring cameras (ip or ajax) and ports (init2)
$.ajax({
url: "multicam2.php?cmd=update&multicam_dir="+multicam_dir+
"&multicam_rperiod="+multicam_rperiod+"&multicam_speriod="+multicam_speriod,
success: function(){
console.log("Written configs to the master camera");
}
});
}
function setDirMulti(){
// url = "camogm_interface.php?cmd=dir_prefix&name=" + multicam_dir;
url = "camogm_interface.php?cmd=set_prefix&name=" + multicam_dir; // can not mkdir before mounted
console.log("setDirMulti(), url="+url);
multi_ajax(url,function(){
console.log(this.ip+": dir_prefix " + multicam_dir);
});
}
var appply_pending = 0;
function setDirSingle(cam_i){ // set recording directory for one camera (after starting camogm), request status
console.log("csetDirSingle("+cam_i+")");
$.ajax({
// url: "http://"+cams[cam_i].ip+"/camogm_interface.php?cmd=dir_prefix&name=" + multicam_dir, // should never be done before partition is mounted!
url: "http://"+cams[cam_i].ip+"/camogm_interface.php?cmd=SET_prefix&name=" + multicam_dir, // can not mkdir before mounted
cam_i: cam_i,
success: function(){
console.log(cams[this.cam_i].ip+": set directory to " + multicam_dir);
check_camogm_status(this.cam_i);
}
});
}
// LWIR16
/*
function LWIR16_sendStatusRequest(){
var url = "http://192.168.0.41/lwir16/lwir16.php?daemon=status";
// if (apply_pending) { // TODO: implement
// var mod_pars = modParameters();
// for(const p in mod_pars) {
// url +="&"+p+"="+mod_pars[p];
// }
// update_editable = true; // will update edited fields
// apply_pending = false;
// }
$.ajax({
url: url,
success: function(){ // was parseStatusResponse()
console.log(cams[this.cam_i].ip+": set directory to " + multicam_dir);
check_camogm_status(this.cam_i);
}
}).fail(function(data,status){
console.log("LWIR16_sendStatusRequest() failed. Check errors");
// will be checked when sending requests
});
}
function parseStatusResponse(resp, update_editable){
var result = "";
// console.log(resp);
if (update_editable) {
if (resp.getElementsByTagName("pre_delay").length!=0){
pre_delay = parseFloat(resp.getElementsByTagName("pre_delay")[0].childNodes[0].nodeValue);
document.getElementById("idpre_delay").value = pre_delay;
}
if (resp.getElementsByTagName("duration").length!=0){
duration = parseInt(resp.getElementsByTagName("duration")[0].childNodes[0].nodeValue);
document.getElementById("idduration").value = duration;
}
if (resp.getElementsByTagName("duration_eo").length!=0){
duration_eo = parseInt(resp.getElementsByTagName("duration_eo")[0].childNodes[0].nodeValue);
document.getElementById("idduration_eo").value = duration_eo;
}
if (resp.getElementsByTagName("ffc").length!=0){
ffc = parseInt(resp.getElementsByTagName("ffc")[0].childNodes[0].nodeValue);
document.getElementById("idffc").checked = ffc > 0;
}
if (resp.getElementsByTagName("ffc_period").length!=0){
ffc_period = parseFloat(resp.getElementsByTagName("ffc_period")[0].childNodes[0].nodeValue);
document.getElementById("idffc_period").value = ffc_period;
}
if (resp.getElementsByTagName("ffc_groups").length!=0){
ffc_groups = parseInt(resp.getElementsByTagName("ffc_groups")[0].childNodes[0].nodeValue);
document.getElementById("idffc_groups").value = ffc_groups;
}
if (resp.getElementsByTagName("ffc_frames").length!=0){
ffc_frames = parseInt(resp.getElementsByTagName("ffc_frames")[0].childNodes[0].nodeValue);
document.getElementById("idffc_frames").value = ffc_frames;
}
if (resp.getElementsByTagName("compressor_run").length!=0){
compressor_run = parseInt(resp.getElementsByTagName("compressor_run")[0].childNodes[0].nodeValue);
document.getElementById("idcompressor_run").checked = compressor_run > 0;
}
if (resp.getElementsByTagName("debug").length!=0){
debug = parseInt(resp.getElementsByTagName("debug")[0].childNodes[0].nodeValue);
document.getElementById("iddebug").value = debug;
}
}
if (resp.getElementsByTagName("sequence_num").length!=0){
sequence_num = parseInt(resp.getElementsByTagName("sequence_num")[0].childNodes[0].nodeValue);
document.getElementById("idsequence_num").value = sequence_num;
}
if (resp.getElementsByTagName("last_ffc").length!=0){
last_ffc = parseFloat(resp.getElementsByTagName("last_ffc")[0].childNodes[0].nodeValue);
document.getElementById("idlast_ffc").value = last_ffc;
}
if (resp.getElementsByTagName("time_to_ffc").length!=0){
time_to_ffc = parseFloat(resp.getElementsByTagName("time_to_ffc")[0].childNodes[0].nodeValue);
document.getElementById("idtime_to_ffc").value = time_to_ffc;
}
if (resp.getElementsByTagName("capture_run").length!=0){
capture_run = parseInt(resp.getElementsByTagName("capture_run")[0].childNodes[0].nodeValue);
document.getElementById("idcapture_run").checked = capture_run > 0;
if (update_editable) {
document.getElementById("idStartStop").innerHTML=capture_run?"Stop":"Start";
document.getElementById("idStartStop").disabled = false;
want_run = capture_run;
}
}
if (update_editable) {
document.getElementById('idApply').innerHTML='Apply';
document.getElementById('idApply').disabled= false;
document.getElementById('idRestart').innerHTML='Restart';
document.getElementById('idRestart').disabled= false;
}
request_num++;
document.getElementById("idrequest_num").value = request_num;
update_editable = false;
sendStatusRequest();
}
*/
......
<?php
include "../include/elphel_functions_include.php";
$cmd = "donothing";
define('CONF_LWIR16', '/etc/elphel393/lwir16.ini');
define('IPS', 'ips');
define('PORT_MASKS', 'port_masks');
define('NUM_PORTS', 4);
define('IMGSRV_PORT0', 2323);
define('PORTSPATH', '/sys/devices/soc0/elphel393-detect_sensors@0');
define('MULTICAM_INI', 'multicam_ini');
define('MULTICAM_DIR', 'multicam_dir');
define('MULTICAM_RPERIOD', 'multicam_rperiod');
define('MULTICAM_SPERIOD', 'multicam_speriod');
define('MULTICAM_CONF', 'multicam_conf');
if (isset($_GET['cmd']))
$cmd = $_GET['cmd'];
$cmd = $_GET['cmd'];
else if (isset($argv[1]))
$cmd = $argv[1];
$cmd = $argv[1];
///$ini = parse_ini_file(CONF_LWIR16);
//$cmd = "donothing";
$config = "/var/volatile/html/multicam.xml";
//$config = "/var/volatile/html/multicam.xml";
// path to sysfs for port scanning
$portspath = "/sys/devices/soc0/elphel393-detect_sensors@0";
//$portspath = "/sys/devices/soc0/elphel393-detect_sensors@0";
// total number of ports in 10393
$nports = 4;
$port0 = 2323;
//$nports = 4;
//$port0 = 2323;
// extract ip addresses
if ($_SERVER['REQUEST_METHOD']==="POST"){
......@@ -27,30 +41,137 @@ if ($_SERVER['REQUEST_METHOD']==="POST"){
$list = $_SERVER['SERVER_ADDR'];
}
}
$ips = explode(',',$list);
// allow CORS
header('Access-Control-Allow-Origin: *');
// does not need configs
switch($cmd){
case "ports":
print(getports());
break;
case "ports":
xml_header();
print(getports());
exit (0);
}
getIni(); // wil exit OK now
//print ("<!--");
//print_r($lwir16_ini);
//print ("-->");
//exit(0);
switch($cmd){
case "ips":
xml_header();
print(getIps());
exit (0);
case "update":
foreach($_GET as $key=>$value) {
if (array_key_exists($key, $GLOBALS[MULTICAM_INI])){
$GLOBALS[MULTICAM_INI][$key] = $value;
}
}
writeConfig();
exitXmlOK();
exit(0); //
case "configs": // return xml with all $GLOBALS[MULTICAM_INI]
xml_header();
print(getConfigs());
exit(0);
// obsolete old commands
/*
case "write":
// write
// use get and post requests
write_config($config,$ips);
print("ok");
break;
write_config($config,$ips);
print("ok");
break;
case "snapshot":
send_zipped_images($ips);
break;
case "read":
default:
default:
// will never need read - config is in http://camip/var/multicam.xml
// read
print(file_get_contents($config));
/// print(file_get_contents($config));
* */
default:
exitXmlOK("Unknown");
}
function getIni() {
$ini= parse_ini_file(CONF_LWIR16);
// define parameter type here
$GLOBALS[MULTICAM_INI][IPS] = $ini[IPS];
$GLOBALS[MULTICAM_INI][PORT_MASKS] = $ini[PORT_MASKS];
$GLOBALS[MULTICAM_INI][MULTICAM_DIR] = $ini[MULTICAM_DIR];
$GLOBALS[MULTICAM_INI][MULTICAM_RPERIOD] = $ini[MULTICAM_RPERIOD];
$GLOBALS[MULTICAM_INI][MULTICAM_SPERIOD] = $ini[MULTICAM_SPERIOD];
$GLOBALS[MULTICAM_INI][MULTICAM_CONF] = $ini[MULTICAM_CONF];
// copy other default parameters
// try to read xml if exists
$multi_xml = 0;
if (file_exists($ini[MULTICAM_CONF])) {
$multi_xml = simplexml_load_file($ini[MULTICAM_CONF]);
}
if ($multi_xml) { // if file exists
$multi_ini = array();
foreach ( $multi_xml->children () as $entry ) {
$GLOBALS[MULTICAM_INI][$entry->getName ()] = (string) $entry;
}
} else {
writeConfig();
}
/*
print ("<!--");
var_dump($GLOBALS[MULTICAM_INI]);
print ("-->");
exit (0);
*/
if (isset($GLOBALS[MULTICAM_INI][IPS])) {
$GLOBALS[IPS] = explode(',',$GLOBALS[MULTICAM_INI][IPS]);
if (isset ($GLOBALS[MULTICAM_INI][PORT_MASKS])){
$masks = explode(',',$GLOBALS[MULTICAM_INI][PORT_MASKS]);
}
// Add port masks incdexed by IPs. Changing IPs resets masks
$GLOBALS[PORT_MASKS] = array();
for ($i = 0; $i < count($GLOBALS[IPS]); $i++){
if (isset($masks) && (count($masks) > $i)){
$GLOBALS[PORT_MASKS][$GLOBALS[IPS][$i]] = (int) $masks[$i];
} else {
$GLOBALS[PORT_MASKS][$GLOBALS[IPS][$i]] = (1 << NUM_PORTS) -1;
}
}
}
/*
print ("<!--");
var_dump($GLOBALS[MULTICAM_INI]);
echo "------------------------";
var_dump($GLOBALS[IPS]);
echo "------------------------";
var_dump($GLOBALS[PORT_MASKS]);
print ("-->");
exit (0);
*/
// exitXmlOK(); // exit for now
}
function writeConfig(){
$xml="<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n<multicam>\n";
foreach($GLOBALS[MULTICAM_INI] as $key=>$value){
$xml .= "\t<".$key.'>'.$value.'</'.$key.">\n";
}
$xml .= "</multicam>\n";
file_put_contents($GLOBALS[MULTICAM_INI][MULTICAM_CONF],$xml);
exec('sync');
}
function printToComment($str) {
echo "<!--".$str."-->";
}
function write_config($config,$ips){
......@@ -59,7 +180,7 @@ function write_config($config,$ips){
foreach($ips as $ip){
$list .= "\t<camera>$ip</camera>\n";
}
$xml = "<?xml version='1.0' standalone='yes'?>\n";
$xml = "<?xml version='1.0' encoding=\"iso-8859-1\" standalone='yes'?>\n";
$xml .= "<Document>\n";
$xml .= $list;
$xml .= "</Document>\n";
......@@ -69,32 +190,108 @@ function write_config($config,$ips){
return 0;
}
function getports(){
function getConfigs(){
$xml .= "<configs>\n";
// Add more when available. not Using foreach to keep order
$xml .= xmlEntry(MULTICAM_DIR, $GLOBALS[MULTICAM_INI][MULTICAM_DIR], 1);
$xml .= xmlEntry(MULTICAM_RPERIOD, $GLOBALS[MULTICAM_INI][MULTICAM_RPERIOD], 1);
$xml .= xmlEntry(MULTICAM_SPERIOD, $GLOBALS[MULTICAM_INI][MULTICAM_SPERIOD], 1);
$xml .= "</configs>\n";
return $xml;
}
global $nports, $portspath, $port0;
$res = "\t<camera ip='".$_SERVER['SERVER_ADDR']."'>\n";
for($i=0;$i<$nports;$i++){
$sensor = $portspath."/sensor{$i}0";
function getports(){
$res = "\t<camera ip='".$_SERVER['SERVER_ADDR']."'>\n";
for($i=0;$i<NUM_PORTS;$i++){
$sensor = PORTSPATH."/sensor{$i}0";
// the file is always there actually
if(is_file($sensor)){
$c = trim(file_get_contents($sensor));
$p = $port0+$i;
$p = IMGSRV_PORT0+$i;
$res .= "\t\t<port index='$i' port='$p'>$c</port>\n";
}
}
$res .= "\t</camera>\n";
$xml = "<?xml version='1.0' standalone='yes'?>\n";
$xml .= "<Document>\n";
$xml .= $res;
$xml .= "</Document>\n";
return $xml;
}
function getIps(){
//$GLOBALS[IPS]
$xml = "<cameras>\n";
for($i=0;$i< count($GLOBALS[IPS]);$i++){
$xml .= "\t<ip index='$i'>".$GLOBALS[IPS][$i]."</ip>\n";
}
$xml .= "</cameras>\n";
return $xml;
}
function exitXmlOK($str="ok"){
xml_header();
print ('<'.$str.'/>');
exit(0);
}
function xml_header() {
header("Content-type: text/xml");
header("Pragma: no-cache\n");
// allow CORS: needed for multi cams unified control
header('Access-Control-Allow-Origin: *');
echo "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n";
}
function xmlEntry($key,$value,$ind=0){
$res = '';
for ($i = 0; $i < $ind; $i++) $res.="\t";
$res .= '<'.$key.'>'.$value.'</'.$key.">\n";
return $res;
}
function applyConf($arr){
//port_masks= "15,15,15,15,15"
if (isset($arr[IPS])) {
$GLOBALS[IPS] = explode(',',$arr[IPS]);
if (isset ($arr[PORT_MASKS])){
$masks = explode(',',$arr[PORT_MASKS]);
}
// Add port masks incdexed by IPs. Changing IPs resets masks
$GLOBALS[PORT_MASKS] = array();
for ($i = 0; $i < count($GLOBALS[IPS]); $i++){
if (isset($masks) && (count($masks) > $i)){
$GLOBALS[PORT_MASKS][$GLOBALS[IPS][$i]] = (int) $masks[$i];
} else {
$GLOBALS[PORT_MASKS][$GLOBALS[IPS][$i]] = (1 << NUM_PORTS) -1;
}
}
}
/*
if (isset($arr[DURATION])){
$GLOBALS[DURATION] = (int) $arr[DURATION];
$GLOBALS[DURATION_EO] = (int) ($GLOBALS[DURATION]/EO_DECIMATE+1);
}
if (isset($arr[DURATION_EO])) $GLOBALS[DURATION_EO] = (int) $arr[DURATION_EO];
if (isset($arr[PRE_DELAY])) $GLOBALS[PRE_DELAY] = (double) $arr[PRE_DELAY];
if (isset($arr[FFC_PERIOD])) $GLOBALS[FFC_PERIOD] = (double) $arr[FFC_PERIOD];
if (isset($arr[FFC_GROUPS])) $GLOBALS[FFC_GROUPS] = (int) $arr[FFC_GROUPS];
if (isset($arr[FFC_FRAMES])) $GLOBALS[FFC_FRAMES] = (int) $arr[FFC_FRAMES];
if (isset($arr[DAEMON_CMD])) $GLOBALS[DAEMON_CMD] = $arr[DAEMON_CMD];
if (isset($arr[DEBUG])) $GLOBALS[DEBUG] = (int) $arr[DEBUG];
if (isset($arr[COMPRESSOR_RUN])) $GLOBALS[COMPRESSOR_RUN] = (int) $arr[COMPRESSOR_RUN]; // only after INIT
if (isset($arr[FFC])) $GLOBALS[FFC] = $arr[FFC]?1:0;
if (isset($arr[TIFF_TELEM])) $GLOBALS[TIFF_TELEM] = (int) $arr[TIFF_TELEM];
if (isset($arr[TIFF_MN])) $GLOBALS[TIFF_MN] = (int) $arr[TIFF_MN];
if (isset($arr[TIFF_MX])) $GLOBALS[TIFF_MX] = (int) $arr[TIFF_MX];
if (isset($arr[TIFF_BIN_SHIFT])) $GLOBALS[TIFF_BIN_SHIFT] = (int) $arr[TIFF_BIN_SHIFT];
if (isset($arr[TIFF_AUTO])) $GLOBALS[TIFF_AUTO] = (int) $arr[TIFF_AUTO];
*/
}
function send_zipped_images($ips){
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment