<commit_before>/**
\ * Manager for Stratified step
 * @author M. Togna
 */
ReportingUnitManager = function( container , sdERDManager , stepNo ){

	var onUpload = $.proxy( this.showImport , this );
	var dataProvider 	= new CsvFileDataProvider( onUpload , false);
	dataProvider.setTableInfo( new ReportingUnitManager.prototype.tableInfo() );
	dataProvider.tableAlias = 'Reporting Unit (AOI)';
	dataProvider.tableType	= CsvFileDataProvider.AOI_TABLE_TYPE;

	SamplingDesignStepManager.call( this, container , sdERDManager , stepNo , dataProvider );

	EventBus.addEventListener( "calc.sampling-design.base-unit-change", this.updateJoin , this );
	EventBus.addEventListener( "calc.sampling-design.stratified-change", this.updateJoin, this );
	EventBus.addEventListener( "calc.sampling-design.two-phases-change", this.updateJoin, this );
	EventBus.addEventListener( "calc.sampling-design.phase1-table-change", this.updateJoin , this );
	EventBus.addEventListener( "calc.sampling-design.two-stages-change", this.updateJoin, this );
	EventBus.addEventListener( "calc.sampling-design.psu-table-change", this.updateJoin , this );

	this.join						= new ERDTableJoin( 'aoi-join' );
	this.join.multiple 				= false;
	this.join.rightColumnsReadOnly 	= true;
	this.join.leftJoinPointCssClass = 'anchor-right';
	this.join.onChange				= $.proxy( this.onJoinChange , this );

	this.joinColumnStepNo = -1;

	this.addJoin( this.join );

	this.update();
};
ReportingUnitManager.prototype 				= Object.create(SamplingDesignStepManager.prototype);
ReportingUnitManager.prototype.constructor 	= ReportingUnitManager;

ReportingUnitManager.prototype.show = function(){
	this.container.fadeIn();
	this.highlight();
};

ReportingUnitManager.prototype.update = function(){

	if( this.sd().samplingUnitId  ){
		this.show();
		this.updateJoin();
//		this.join.show();
	} else {
//		this.container.hide();
//		this.join.hide();
	}

};

ReportingUnitManager.prototype.erdJoinSettings = function(){
//	{
//		"rightTable": {"table":"plot"},
//		"columns"	:[{"left":"cluster","right":"id"},{"left":"plot","right":"no"}],
//		"leftTable"	:{"schema":"calc","table":"_phase1_plot_naforma_20151216"}
//	}

	var joinSettings = {};
	joinSettings.rightTable = {"table":"aoi" , "schema":"calc"};
	joinSettings.columns 	= [ {"left": null, "right": this.joinColumnName()} ];

	if( this.sd().aoiJoinSettings ){
		joinSettings.leftTable 			= { "table":this.sd().aoiJoinSettings.table, "schema":this.sd().aoiJoinSettings.schema };
		joinSettings.columns[0].left 	= this.sd().aoiJoinSettings.column;
	}

	return joinSettings;
};
ReportingUnitManager.prototype.updateJoin = function(){

	this.join.disconnect();

	var leftTable = null;
	if( this.sd().twoPhases === true ){
		leftTable = (this.sdERDManager.twoPhasesManager) ? this.sdERDManager.twoPhasesManager.table : null;
	} else if( this.sd().twoStages === true ){
		leftTable = (this.sdERDManager.psuManager) ? this.sdERDManager.psuManager.table : null;
	} else if( this.sdERDManager.baseUnitManager ){
		leftTable = this.sdERDManager.baseUnitManager.table;
	}

	var firstEdit 	= !this.sd().id;
	var show 		= ( this.joinColumnStepNo == this.currentStepNo ) || !firstEdit;

	if( leftTable && leftTable.dataProvider.getTableInfo() && show){
		this.join.setRightTable( this.table );
		this.join.setLeftTable( leftTable );

		this.join.connect( this.erdJoinSettings() );

		this.updateEditMode();
	}

};
ReportingUnitManager.prototype.showEditStep = function( evt, stepNo ){

	SamplingDesignStepManager.prototype.showEditStep.call( this , evt , stepNo );

//	if( this.sdERDManager.editMode && this.currentStepNo == this.stepNo && !this.sd().id){
//		this.update();
//	}

	if( stepNo == this.joinColumnStepNo ){
		this.update();
		this.join.highlight();
		this.join.setEditMode( true );
	} else {
		this.join.setEditMode( false );
	}
};
ReportingUnitManager.prototype.joinColumnName = function(){
	var colName = ''
	WorkspaceManager.getInstance().activeWorkspace( function(ws){
		if( ws.aoiHierarchies && ws.aoiHierarchies.length > 0 ){
			var levels = ws.aoiHierarchies[ 0 ].levels;
			var level = levels[ levels.length -1 ];
			colName = level.caption + ' Code';
		}
	});

	return colName;
};

ReportingUnitManager.prototype.onJoinChange = function(){
	var sdStratumJoinSettings = {};

	var erdJoinSettings = this.join.jsonSettings();
	if( erdJoinSettings.leftTable ){
		sdStratumJoinSettings.table 	= erdJoinSettings.leftTable.table;
		sdStratumJoinSettings.schema 	= erdJoinSettings.leftTable.schema;
		sdStratumJoinSettings.column 	= '';
		if( erdJoinSettings.columns && erdJoinSettings.columns.length >0 ){
			var col = erdJoinSettings.columns[ 0 ];
			sdStratumJoinSettings.column 	= col.left;
		}
	}

	 this.sd().aoiJoinSettings = sdStratumJoinSettings;

};

ReportingUnitManager.prototype.tableInfo = function(){
	this.table 			= "aoi";
	this.schema			= 'calc';
	this.fields 		= {};
	this.fields.table 	= "aoi";
	this.fields.schema 	= 'calc';
	this.fields.columns = [];

	this.fields.columns.push( { 'column_name' : ReportingUnitManager.prototype.joinColumnName() } );
};

ReportingUnitManager.prototype.showImport = function(response){
	var filePath = response.fields.filepath;

	var $this = this;
	var modalDiv 		= $( '#aoi-import-column-selector' );
	var onImport		= function(){
		modalDiv.modal( 'hide' );
		EventBus.dispatch( "calc.sampling-design.reporting-unit-table-change", null );
		$this.dataProvider.setTableInfo( new ReportingUnitManager.prototype.tableInfo() );
		$this.updateJoin();
	};
	var importer		= new ReportingUnitImportManager( modalDiv , onImport , response );

	modalDiv.modal( 'show' );
};<commit_msg>sd bug fixes<commit_after>/**
\ * Manager for Stratified step
 * @author M. Togna
 */
ReportingUnitManager = function( container , sdERDManager , stepNo ){

	var onUpload = $.proxy( this.showImport , this );
	var dataProvider 	= new CsvFileDataProvider( onUpload , false);
	dataProvider.setTableInfo( new ReportingUnitManager.prototype.tableInfo() );
	dataProvider.tableAlias = 'Reporting Unit (AOI)';
	dataProvider.tableType	= CsvFileDataProvider.AOI_TABLE_TYPE;

	SamplingDesignStepManager.call( this, container , sdERDManager , stepNo , dataProvider );

	EventBus.addEventListener( "calc.sampling-design.base-unit-change", this.updateJoin , this );
	EventBus.addEventListener( "calc.sampling-design.stratified-change", this.updateJoin, this );
	EventBus.addEventListener( "calc.sampling-design.two-phases-change", this.updateJoin, this );
	EventBus.addEventListener( "calc.sampling-design.phase1-table-change", this.updateJoin , this );
	EventBus.addEventListener( "calc.sampling-design.two-stages-change", this.updateJoin, this );
	EventBus.addEventListener( "calc.sampling-design.psu-table-change", this.updateJoin , this );

	this.join						= new ERDTableJoin( 'aoi-join' );
	this.join.multiple 				= false;
	this.join.rightColumnsReadOnly 	= true;
	this.join.leftJoinPointCssClass = 'anchor-right';
	this.join.onChange				= $.proxy( this.onJoinChange , this );

	this.joinColumnStepNo = -1;

	this.addJoin( this.join );

	this.update();
};
ReportingUnitManager.prototype 				= Object.create(SamplingDesignStepManager.prototype);
ReportingUnitManager.prototype.constructor 	= ReportingUnitManager;

ReportingUnitManager.prototype.show = function(){
	this.container.fadeIn();
	this.highlight();
};

ReportingUnitManager.prototype.update = function(){

	if( this.sd().samplingUnitId  ){
		this.show();
		this.updateJoin();
//		this.join.show();
	} else {
//		this.container.hide();
//		this.join.hide();
	}

};

ReportingUnitManager.prototype.erdJoinSettings = function(){
//	{
//		"rightTable": {"table":"plot"},
//		"columns"	:[{"left":"cluster","right":"id"},{"left":"plot","right":"no"}],
//		"leftTable"	:{"schema":"calc","table":"_phase1_plot_naforma_20151216"}
//	}

	var joinSettings = {};
	joinSettings.rightTable = {"table":"aoi" , "schema":"calc"};
	joinSettings.columns 	= [ {"left": null, "right": this.joinColumnName()} ];

	if( this.sd().aoiJoinSettings ){
		joinSettings.leftTable 			= { "table":this.sd().aoiJoinSettings.table, "schema":this.sd().aoiJoinSettings.schema };
		joinSettings.columns[0].left 	= this.sd().aoiJoinSettings.column;
	}

	return joinSettings;
};
ReportingUnitManager.prototype.updateJoin = function(){

	this.join.disconnect();

	var leftTable = null;
	if( this.sd().twoPhases === true ){
		leftTable = (this.sdERDManager.twoPhasesManager) ? this.sdERDManager.twoPhasesManager.table : null;
	} else if( this.sd().twoStages === true ){
		leftTable = (this.sdERDManager.psuManager) ? this.sdERDManager.psuManager.table : null;
	} else if( this.sdERDManager.baseUnitManager ){
		leftTable = this.sdERDManager.baseUnitManager.table;
	}

	var firstEdit 	= !this.sd().id;
	var show 		= ( this.joinColumnStepNo == this.currentStepNo ) || !firstEdit;

	if( leftTable && leftTable.dataProvider.getTableInfo() && show){
		this.join.setRightTable( this.table );
		this.join.setLeftTable( leftTable );

		this.join.connect( this.erdJoinSettings() );
		this.join.setEditMode( false );
//		this.updateEditMode();
	}

};
ReportingUnitManager.prototype.showEditStep = function( evt, stepNo ){

	SamplingDesignStepManager.prototype.showEditStep.call( this , evt , stepNo );

//	if( this.sdERDManager.editMode && this.currentStepNo == this.stepNo && !this.sd().id){
//		this.update();
//	}

	if( stepNo == this.joinColumnStepNo ){
		this.update();
		this.join.highlight();
		this.join.setEditMode( true );
	} else {
		this.join.setEditMode( false );
	}
};
ReportingUnitManager.prototype.joinColumnName = function(){
	var colName = ''
	WorkspaceManager.getInstance().activeWorkspace( function(ws){
		if( ws.aoiHierarchies && ws.aoiHierarchies.length > 0 ){
			var levels = ws.aoiHierarchies[ 0 ].levels;
			var level = levels[ levels.length -1 ];
			colName = level.caption + ' Code';
		}
	});

	return colName;
};

ReportingUnitManager.prototype.onJoinChange = function(){
	var sdStratumJoinSettings = {};

	var erdJoinSettings = this.join.jsonSettings();
	if( erdJoinSettings.leftTable ){
		sdStratumJoinSettings.table 	= erdJoinSettings.leftTable.table;
		sdStratumJoinSettings.schema 	= erdJoinSettings.leftTable.schema;
		sdStratumJoinSettings.column 	= '';
		if( erdJoinSettings.columns && erdJoinSettings.columns.length >0 ){
			var col = erdJoinSettings.columns[ 0 ];
			sdStratumJoinSettings.column 	= col.left;
		}
	}

	 this.sd().aoiJoinSettings = sdStratumJoinSettings;

};

ReportingUnitManager.prototype.tableInfo = function(){
	this.table 			= "aoi";
	this.schema			= 'calc';
	this.fields 		= {};
	this.fields.table 	= "aoi";
	this.fields.schema 	= 'calc';
	this.fields.columns = [];

	this.fields.columns.push( { 'column_name' : ReportingUnitManager.prototype.joinColumnName() } );
};

ReportingUnitManager.prototype.showImport = function(response){
	var filePath = response.fields.filepath;

	var $this = this;
	var modalDiv 		= $( '#aoi-import-column-selector' );
	var onImport		= function(){
		modalDiv.modal( 'hide' );
		EventBus.dispatch( "calc.sampling-design.reporting-unit-table-change", null );
		$this.dataProvider.setTableInfo( new ReportingUnitManager.prototype.tableInfo() );
		$this.updateJoin();
	};
	var importer		= new ReportingUnitImportManager( modalDiv , onImport , response );

	modalDiv.modal( 'show' );
};<|endoftext|>


<commit_before>//
// Copyright (c) 2013, Ibisense ltd.
// see file LICENSE for licensing (BSD)
//
"use strict";

global.jQuery = require('jquery');

var sys = require('sys');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var fs = require('fs');
var ibisense = require("../lib/ibisense.api.js");
var channelSlugs = {};
var config_file = "../etc/ibideviced_config.json";

//Configuration
var config = JSON.parse(fs.readFileSync("../etc/ibideviced_config.json"));

if (!config) {
    console.log("No configuration file was found. Exiting.");
    process.exit(-1);
}

//Init Ibisense API
ibisense.setApiKey(config.apikey);

//Logging
var log4js = require("log4js");
log4js.configure({
	"appenders" : [
		       { "type" : "file",
			       "filename" : config.log_dir+'/ibideviced.log' ,
			       "maxLogSize" : 1048576,
			       "backups" : 3,
			       "category" : "ibideviced"
			       }
			],
	    });

var log = log4js.getLogger("ibideviced");
log.setLevel(log4js.levels.INFO);

//Debug on / off ?
//log.setLevel(log4js.levels.TRACE)

//Here are the data collecting scripts
var collectors_path = config.collectors_dir;
if(collectors_path[collectors_path.length-1] !== '/') {
    collectors_path = collectors_path + "/";
}

//Checks if the collector file looks runnable. TBD: check file permissions for +x
var collector_runnable = function(name) {
    //TODO: add check for executable and non-directory
    //Do not run backup files
    if(name.match(/^\.$/) || name.match(/\#/) || name.match(/~$/)) {
	return false;
    } else {
	return true;
    }
}

//Run all the collectors in the directory
var runCollectors = function() {
    try {
	var collectors = fs.readdirSync(collectors_path);
    } catch (e) {
	//TODO: add exception handling
    }

    for (var c_idx in collectors) {
	var collector = collectors[c_idx];
	log.trace(collector);
	if(collector_runnable(collector)) {
	    execCollector(collectors_path + collector, null);
	}
    }
};

//Look up CUID for a measurement
//Create new channel
var createChannel = function(ts, chName, chValue) {
    var ch = new ibisense.models.Channel({
	    "name" : "RPi sensor " + chName,
	    "description" : "",
	    "unit" : "",
	    "abbreviation" : chName
	});

    log.info("New channel: " + ch.toJsonString());

    ibisense.channels.add(config.sensorid,
			  ch,
			  function(newch, status) {
			      //New channel created - store CUID and repost data
			      log.info("Created new channel '" + newch.cuid() + "' for sensor '" + chName + "'");
			      channelSlugs[chName] = newch.cuid();
			      measurementReceived(ts, chName, chValue);
			  },
			  function () {
			      //Error
			      log.error("Can't create new channel " + chName);
			  });
    ;

}

//A new measurement received event
var measurementReceived = function(ts,chName,chValue)  {
    var m = { 'abs_time' : ts,
	      'value' : chValue };

    //Do we already know the Ibisense Channel ?
    if(channelSlugs[chName]) {
	//If the CUID is known, send the data to Ibisense
	ibiSendMeasurement(channelSlugs[chName], m);
    } else {
	//If CUID (Channel UID) is unknown, poll for it. If not known, create channel
	//measurementUnknownCUID(chname, m);
	log.warn("Unknown CUID for " + chName);
	createChannel(ts, chName, chValue);
    }
}

// Processes results from collectors

//Process data formats like:
// T,20

// T1,20.34 °C
// T2,34.22 °C

// V1,12.00V
// RH,34 %

// Use * to denote measurement timestamp if it is not current:
// *02:03:00,T,20
// *02:03:01,T,20

var processCollectorResult = function(path,data) {
    //var lines = data.split("\n");
    var lines = data;
    for (var lineno in lines) {
	var ts = null;
	var line = lines[lineno];
	var lineItems = line.split(/\,/);
	var ts = new Date();
        log.trace("Received new measurement: " + line)
	for(var i in lineItems) {

	    //Is the first one a timestamp ?
	    if (i===0 && lineItems[i].match(/^\*/)) {
		try {
		    ts = new Date(lineItems[0].substring(1));
		} catch (e) {
		    //Error in timestamp
		log.error(path + " can't parse timestamp " + lineItems[0]);
		ts=null;
		}
	    } else {
		//This is a measurement, parse it
		var m, chName, chValue;
		var ll = lineItems[i];

		if(m=ll.match(/^([^,]+?)\s+([+-]?.*)/)) {
		    if(m.length >=3) {
			var chName = m[1];
			var chValue = parseFloat(m[2]);
                        log.trace("line: " + ll + ", chName: " + chName + ", chValue: " + chValue);
                        if (chName && !isNaN(chValue)) {
  	                    measurementReceived(ts,chName,chValue);
                        }
		    }
		    else {
			log.error(path + " value " + lineItems[i] + " does not contain channel name and unit");
		    }
		} else {
		    log.error("Unable to parse line " + line);
		}

	    }

	}

    }
}

//Execute collector and pipe results
var execCollector = function(path, sink) {
    var running = true;
    var stdout_data=[];
    log.trace("Starting " + path);

    //try-catch block catches non-executable files and errors
    try {
	var child = spawn(path);

	child.stdout.on('data', function(d) {
		//Collect data from STDOUT
		stdout_data.push(""+d);
		log.trace("STDOUT " + stdout_data);
	    });

	child.stderr.on('data', function(d) {
		log.error(path + " stderr: " + d);
	    });

	child.on('close', function(code, signal) {
		running = false;
		log.trace(path + " exit: " + code + "/" + signal); running = false;
		if(code === 0) {
		    processCollectorResult(path, stdout_data);
		} else {
		    log.warn(path + " closed with code " + code);
		}
	    });

	//TODO: add timeout+kill. This does not work well with node.js for some reason!
	setTimeout(function() {
		if(running) {
		    child.kill("SIGTERM");
		    log.warn("Sleeping process,killed " + path );
		}
	    }, config.process_timeout);
    } catch (e) {
	log.error(e);
    }
};

//TODO: add data buffering if net connetion is down

// Message handling routines. slug is the same as CUID = channel UID
var ibiSendMeasurement = function(slug, m) {
    var cuid = '';

    if(typeof m.value !== "number") {
	//is it a stringed number ?
	if(!isNaN(parseFloat(m.value))) {
	    //Yes, replace with parsed number
	    m.value = parseFloat(m.value);
	} else {
	    //Only numeric data allowed currently
	    log.trace("Non-numeric measurement: " + m.value);
	    return;
	}
    }

    var dp = new ibisense.models.DataPoint({ "date": m.abs_time,
					     "value": m.value });

    log.trace("Storing " + dp.toJsonString());

    var onError = function(status) {
	log.error("Ibisense store failed with code " + status);
    };

    var onSuccess = function(status) {
	log.trace("Ibisense store OK");
    };

    var onErrorGen = function(dpoint) {
	return function(status) {
	    log.error("Ibisense store failed with code " + status + " for data " + dpoint);
	}
    }

    var onSuccessGen = function (dpoint) {
	return function(status) {
	    log.trace("Stored: " + dpoint);
	};
    };

    if(typeof slug !== 'undefined' && slug && dp.toJson().t && dp.toJson().v) {
	log.trace("Storing " + slug + " = " + dp.toJsonString());
	ibisense.datapoints.add(slug,
				[dp],
				onSuccessGen(slug + "=" +dp.toJsonString()),
				onErrorGen(slug + "=" + dp.toJsonString()),
				null);
    } else {
	    log.warn("Slug not defined for " + m.nodename);
    }
};

var startCollectors = function() {
    setInterval(
		function() {
		    runCollectors();
		}, 5000
		);
};

var bootstrapDelayed = function(retries){
    setTimeout(function() { bootstrap(retries) }, 1000);
};

var bootstrap = function(retries) {

    if(!retries) process.exit(-1);
    else retries--;

    if (config.apikey && config.sensorid) {
	ibisense.setApiKey(config.apikey);
	ibisense.sensors.get(config.sensorid,
		function(sensor) {
			console.log("OK. Sensor was  previously registered. Loading channels.")
			ibisense.channels.list(config.sensorid,
				function(channels) {
					for (var i in channels) {
						var ch = channels[i];
						log.info("Data from sensor '" + ch.abbreviation() + "' will be stored to channel '" + ch.cuid() + "'");
						channelSlugs[ch.abbreviation()] = ch.cuid();
					}
					startCollectors();
				}, function(code) {
					log.error("Error on loading channel data from Ibisense, status=" + code);
				}
			);
		}, function(code) {
			console.log("ERROR. Sensor was not registered (" + code + ")");
			bootstrapDelayed(retries);
		});
    } else {
		exec(config.get_mac_address_command, function (error, stdout, stderr) {
			if (error !== null) {
		    	console.log('exec error: ' + error);
		    	process.exit(-1); //fatal
			}

			var serial = stdout || "unknown";
			serial = serial.replace(/^\s*/, '').replace(/\s*$/, '');
			console.log("Registering device with serial number: " + serial + ".");
			if (serial === "unknown") {
		    	console.log("Could not retrieve device serial number");
		    	process.exit(-1); //fatal
			}

			// By default, use MAC address of the RPi as the registration secret
			// This is not the most secure method, but better than nothing - now
			// you can share the cpuid to others with whom you wish to share the
			// data and they won't be able to fake your data without knowing the MAC
			// address;

			exec(config.get_cpu_number_command, function (error, stdout, stderr) {

				var secret;

				if(config.secret) {
			    	secret = config.secret;
				} else {
			    	secret = stdout || "";
					secret = secret.replace(/^\s*/, '').replace(/\s*$/, '');

					if (secret === "") {
		    			console.log("Using an empty secret");
					}
				}

				log.trace("SECRET=" + secret);
				ibisense.activation.activateUnregistered({
					'serial': serial,
					'secret': secret},
		    		function(apikey, suid) {
						console.log("Device with serial number " + serial + " was registered in the Ibisense cloud: SUID " + suid + " API KEY: " + apikey);
						config.apikey   = apikey;
						config.sensorid = suid;
						ibisense.setApiKey(config.apikey);
						fs.writeFile(config_file, JSON.stringify(config, null, 4),
					    	 function (err) {
								 if (err) {
						     		console.log("There has been an error saving your configuration data.");
						     		console.log(err.message);
						     		bootstrapDelayed(retries);
						 		}
						 		console.log("Configuration saved successfully.")
					   		  	bootstrap(retries); //startCollectors();
				     		}
				     	);
		    		}, function(code) {
						console.log("There was an error registering the device in Ibisense cloud. Error code : " + code);
						bootstrapDelayed(retries);
		    		}
		    	);
		    });
	    });
    }
};

// MAIN - startup

var bootstrapRetries = 10;
bootstrap(bootstrapRetries);

<commit_msg>Changed parsing functionality<commit_after>//
// Copyright (c) 2013, Ibisense ltd.
// see file LICENSE for licensing (BSD)
//
"use strict";

global.jQuery = require('jquery');

var sys = require('sys');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var fs = require('fs');
var ibisense = require("../lib/ibisense.api.js");
var channelSlugs = {};
var config_file = "../etc/ibideviced_config.json";

//Configuration
var config = JSON.parse(fs.readFileSync("../etc/ibideviced_config.json"));

if (!config) {
    console.log("No configuration file was found. Exiting.");
    process.exit(-1);
}

//Init Ibisense API
ibisense.setApiKey(config.apikey);

//Logging
var log4js = require("log4js");
log4js.configure({
	"appenders" : [
		       { "type" : "file",
			       "filename" : config.log_dir+'/ibideviced.log' ,
			       "maxLogSize" : 1048576,
			       "backups" : 3,
			       "category" : "ibideviced"
			       }
			],
	    });

var log = log4js.getLogger("ibideviced");
log.setLevel(log4js.levels.INFO);

//Debug on / off ?
//log.setLevel(log4js.levels.TRACE)

//Here are the data collecting scripts
var collectors_path = config.collectors_dir;
if(collectors_path[collectors_path.length-1] !== '/') {
    collectors_path = collectors_path + "/";
}

//Checks if the collector file looks runnable. TBD: check file permissions for +x
var collector_runnable = function(name) {
    //TODO: add check for executable and non-directory
    //Do not run backup files
    if(name.match(/^\.$/) || name.match(/\#/) || name.match(/~$/)) {
	return false;
    } else {
	return true;
    }
}

//Run all the collectors in the directory
var runCollectors = function() {
    try {
	var collectors = fs.readdirSync(collectors_path);
    } catch (e) {
	//TODO: add exception handling
    }

    for (var c_idx in collectors) {
	var collector = collectors[c_idx];
	log.trace(collector);
	if(collector_runnable(collector)) {
	    execCollector(collectors_path + collector, null);
	}
    }
};

//Look up CUID for a measurement
//Create new channel
var createChannel = function(ts, chName, chValue) {
    var ch = new ibisense.models.Channel({
	    "name" : "RPi sensor " + chName,
	    "description" : "",
	    "unit" : "",
	    "abbreviation" : chName
	});

    log.info("New channel: " + ch.toJsonString());

    ibisense.channels.add(config.sensorid,
			  ch,
			  function(newch, status) {
			      //New channel created - store CUID and repost data
			      log.info("Created new channel '" + newch.cuid() + "' for sensor '" + chName + "'");
			      channelSlugs[chName] = newch.cuid();
			      measurementReceived(ts, chName, chValue);
			  },
			  function () {
			      //Error
			      log.error("Can't create new channel " + chName);
			  });
    ;

}

//A new measurement received event
var measurementReceived = function(ts,chName,chValue)  {
    var m = { 'abs_time' : ts,
	      'value' : chValue };

    //Do we already know the Ibisense Channel ?
    if(channelSlugs[chName]) {
	//If the CUID is known, send the data to Ibisense
	ibiSendMeasurement(channelSlugs[chName], m);
    } else {
	//If CUID (Channel UID) is unknown, poll for it. If not known, create channel
	//measurementUnknownCUID(chname, m);
	log.warn("Unknown CUID for " + chName);
	createChannel(ts, chName, chValue);
    }
}

// Processes results from collectors

//Process data formats like:
// T,20

// T1,20.34 °C
// T2,34.22 °C

// V1,12.00V
// RH,34 %

// Use * to denote measurement timestamp if it is not current:
// *02:03:00,T,20
// *02:03:01,T,20

//Currently supported formats:
//No timestamp:
//  ChannelName ChannelValue
//  ChannelName,ChannelValue
//With timestamp:
//  Timestamp,ChannelName,ChannelValue
//  Timestamp ChannelName ChannelValue

var processCollectorResult = function(path,lines) {
	if (typeof lines  === 'undefined' || !(a instanceof Array)) return;
	lines.forEach(function(line) {
		//Try to split the line by comma
		var lineItems = line.split(/\s+|\,/);
		var ts = new Date(), chName, chValue;
		var skip = false;
		log.trace("Received new measurement: " + line);
		if (lineItems.length == 2) {
			//we have exactly 2 items after splitting,
			//this indicates that there is only a channel name and value
			try {
				chName  = lineItems[0];
				chValue = parseFloat(lineItems[1]);
				if (isNaN(chValue)) throw new Error("Unparsable floating point value");
			} catch (e) {
				//Error in timestamp
				log.error(path + " Could not parse value " + e);
				skip = true;
			}
		} else if (lineItems.length == 3) {
			//we have exactly 3 items after splitting,
			//this indicates that there is a timestamp, channel name and value
			try {
				ts      = new Date(lineItems[0]);
				chName  = lineItems[1];
				chValue = parseFloat(lineItems[2]);
				if (isNaN(chValue)) throw new Error("Unparsable floating point value");
			} catch (e) {
				//Error in timestamp
				log.error(path + " Could not parse timestamp value " + ts);
				skip = true;
			}
		} else {
			//well we have got something weird, so skip it
			log.error("Got unparsable string, skipping. Faulty string is " + line);
			skip = true;
		}
		if (!skip) measurementReceived(ts,chName,chValue);
	});
}

//Execute collector and pipe results
var execCollector = function(path, sink) {
    var running = true;
    var stdout_data=[];
    log.trace("Starting " + path);

    //try-catch block catches non-executable files and errors
    try {
	var child = spawn(path);

	child.stdout.on('data', function(d) {
		//Collect data from STDOUT
		stdout_data.push(""+d);
		log.trace("STDOUT " + stdout_data);
	    });

	child.stderr.on('data', function(d) {
		log.error(path + " stderr: " + d);
	    });

	child.on('close', function(code, signal) {
		running = false;
		log.trace(path + " exit: " + code + "/" + signal); running = false;
		if(code === 0) {
		    processCollectorResult(path, stdout_data);
		} else {
		    log.warn(path + " closed with code " + code);
		}
	    });

	//TODO: add timeout+kill. This does not work well with node.js for some reason!
	setTimeout(function() {
		if(running) {
		    child.kill("SIGTERM");
		    log.warn("Sleeping process,killed " + path );
		}
	    }, config.process_timeout);
    } catch (e) {
	log.error(e);
    }
};

//TODO: add data buffering if net connetion is down

// Message handling routines. slug is the same as CUID = channel UID
var ibiSendMeasurement = function(slug, m) {
    var cuid = '';

    if(typeof m.value !== "number") {
	//is it a stringed number ?
	if(!isNaN(parseFloat(m.value))) {
	    //Yes, replace with parsed number
	    m.value = parseFloat(m.value);
	} else {
	    //Only numeric data allowed currently
	    log.trace("Non-numeric measurement: " + m.value);
	    return;
	}
    }

    var dp = new ibisense.models.DataPoint({ "date": m.abs_time,
					     "value": m.value });

    log.trace("Storing " + dp.toJsonString());

    var onError = function(status) {
	log.error("Ibisense store failed with code " + status);
    };

    var onSuccess = function(status) {
	log.trace("Ibisense store OK");
    };

    var onErrorGen = function(dpoint) {
	return function(status) {
	    log.error("Ibisense store failed with code " + status + " for data " + dpoint);
	}
    }

    var onSuccessGen = function (dpoint) {
	return function(status) {
	    log.trace("Stored: " + dpoint);
	};
    };

    if(typeof slug !== 'undefined' && slug && dp.toJson().t && dp.toJson().v) {
	log.trace("Storing " + slug + " = " + dp.toJsonString());
	ibisense.datapoints.add(slug,
				[dp],
				onSuccessGen(slug + "=" +dp.toJsonString()),
				onErrorGen(slug + "=" + dp.toJsonString()),
				null);
    } else {
	    log.warn("Slug not defined for " + m.nodename);
    }
};

var startCollectors = function() {
    setInterval(
		function() {
		    runCollectors();
		}, 5000
		);
};

var bootstrapDelayed = function(retries){
    setTimeout(function() { bootstrap(retries) }, 1000);
};

var bootstrap = function(retries) {

    if(!retries) process.exit(-1);
    else retries--;

    if (config.apikey && config.sensorid) {
	ibisense.setApiKey(config.apikey);
	ibisense.sensors.get(config.sensorid,
		function(sensor) {
			console.log("OK. Sensor was  previously registered. Loading channels.")
			ibisense.channels.list(config.sensorid,
				function(channels) {
					for (var i in channels) {
						var ch = channels[i];
						log.info("Data from sensor '" + ch.abbreviation() + "' will be stored to channel '" + ch.cuid() + "'");
						channelSlugs[ch.abbreviation()] = ch.cuid();
					}
					startCollectors();
				}, function(code) {
					log.error("Error on loading channel data from Ibisense, status=" + code);
				}
			);
		}, function(code) {
			console.log("ERROR. Sensor was not registered (" + code + ")");
			bootstrapDelayed(retries);
		});
    } else {
		exec(config.get_mac_address_command, function (error, stdout, stderr) {
			if (error !== null) {
		    	console.log('exec error: ' + error);
		    	process.exit(-1); //fatal
			}

			var serial = stdout || "unknown";
			serial = serial.replace(/^\s*/, '').replace(/\s*$/, '');
			console.log("Registering device with serial number: " + serial + ".");
			if (serial === "unknown") {
		    	console.log("Could not retrieve device serial number");
		    	process.exit(-1); //fatal
			}

			// By default, use MAC address of the RPi as the registration secret
			// This is not the most secure method, but better than nothing - now
			// you can share the cpuid to others with whom you wish to share the
			// data and they won't be able to fake your data without knowing the MAC
			// address;

			exec(config.get_cpu_number_command, function (error, stdout, stderr) {

				var secret;

				if(config.secret) {
			    	secret = config.secret;
				} else {
			    	secret = stdout || "";
					secret = secret.replace(/^\s*/, '').replace(/\s*$/, '');

					if (secret === "") {
		    			console.log("Using an empty secret");
					}
				}

				log.trace("SECRET=" + secret);
				ibisense.activation.activateUnregistered({
					'serial': serial,
					'secret': secret},
		    		function(apikey, suid) {
						console.log("Device with serial number " + serial + " was registered in the Ibisense cloud: SUID " + suid + " API KEY: " + apikey);
						config.apikey   = apikey;
						config.sensorid = suid;
						ibisense.setApiKey(config.apikey);
						fs.writeFile(config_file, JSON.stringify(config, null, 4),
					    	 function (err) {
								 if (err) {
						     		console.log("There has been an error saving your configuration data.");
						     		console.log(err.message);
						     		bootstrapDelayed(retries);
						 		}
						 		console.log("Configuration saved successfully.")
					   		  	bootstrap(retries); //startCollectors();
				     		}
				     	);
		    		}, function(code) {
						console.log("There was an error registering the device in Ibisense cloud. Error code : " + code);
						bootstrapDelayed(retries);
		    		}
		    	);
		    });
	    });
    }
};

// MAIN - startup

var bootstrapRetries = 10;
bootstrap(bootstrapRetries);

<|endoftext|>


<commit_before>//
// Copyright (c) 2013, Ibisense ltd.
// see file LICENSE for licensing (BSD)
//
"use strict";

global.jQuery = require('jquery');

var sys = require('sys');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var fs = require('fs');
var ibisense = require("../lib/ibisense.api.js");
var channelSlugs = {};
var config_file = "../etc/ibideviced_config.json";

//Configuration
var config = JSON.parse(fs.readFileSync("../etc/ibideviced_config.json"));

if (!config) {
    console.log("No configuration file was found. Exiting.");
    process.exit(-1);
}

//Init Ibisense API
ibisense.setApiKey(config.apikey);

//Logging
var log4js = require("log4js");
log4js.configure({
    "appenders": [{
        "type": "file",
        "filename": config.log_dir + '/ibideviced.log',
        "maxLogSize": 1048576,
        "backups": 3,
        "category": "ibideviced"
    }],
});

var log = log4js.getLogger("ibideviced");
log.setLevel(log4js.levels.INFO);

//Debug on / off ?
//log.setLevel(log4js.levels.TRACE)

//Here are the data collecting scripts
var collectors_path = config.collectors_dir;
if (collectors_path[collectors_path.length - 1] !== '/') {
    collectors_path = collectors_path + "/";
}

//Checks if the collector file looks runnable. TBD: check file permissions for +x
var collector_runnable = function(name) {
    //TODO: add check for executable and non-directory
    //Do not run backup files
    if (name.match(/^\.$/) || name.match(/\#/) || name.match(/~$/)) {
        return false;
    } else {
        return true;
    }
}

//Run all the collectors in the directory
var runCollectors = function() {
    try {
        var collectors = fs.readdirSync(collectors_path);
    } catch (e) {
        //TODO: add exception handling
    }

    for (var c_idx in collectors) {
        var collector = collectors[c_idx];
        log.trace(collector);
        if (collector_runnable(collector)) {
            execCollector(collectors_path + collector, null);
        }
    }
};

//Look up CUID for a measurement
//Create new channel
var createChannel = function(ts, chName, chValue) {
    var ch = new ibisense.models.Channel({
        "name": "RPi sensor " + chName,
        "description": "",
        "unit": "",
        "abbreviation": chName
    });

    log.warn("New channel: " + ch.toJsonString());
    ibisense.channels.add(config.sensorid,
        ch,
        function(newch, status) {
            //New channel created - store CUID and repost data
            log.info("Created new channel '" + newch.cuid() + "' for sensor '" + chName + "'");
            channelSlugs[chName] = newch.cuid();
            measurementReceived(ts, chName, chValue);
        },
        function() {
            //Error
            log.error("Can't create new channel " + chName);
        });;

}

//A new measurement received event
var measurementReceived = function(ts, chName, chValue) {
    var m = {
        'abs_time': ts,
        'value': chValue
    };

    //Do we already know the Ibisense Channel ?
    if (channelSlugs[chName]) {
        //If the CUID is known, send the data to Ibisense
        ibiSendMeasurement(channelSlugs[chName], m);
    } else {
        //If CUID (Channel UID) is unknown, poll for it. If not known, create channel
        //measurementUnknownCUID(chname, m);
        log.warn("Unknown CUID for " + chName);
        createChannel(ts, chName, chValue);
    }
}

// Processes results from collectors

//Process data formats like:
// T,20

// T1,20.34 °C
// T2,34.22 °C

// V1,12.00V
// RH,34 %

// Use * to denote measurement timestamp if it is not current:
// *02:03:00,T,20
// *02:03:01,T,20

//Currently supported formats:
//No timestamp:
//  ChannelName ChannelValue
//  ChannelName,ChannelValue
//With timestamp:
//  Timestamp,ChannelName,ChannelValue
//  Timestamp ChannelName ChannelValue

var processCollectorResult = function(path, lines) {
    lines.forEach(function(line) {
        //Try to split the line by comma
        var lineItems = line.replace(/^\s+|\s+$/g, '').split(/\s+|\,/);
        var ts = new Date(),
            chName, chValue;
        var skip = false;
        log.trace("Received new measurement: " + line);
        if (lineItems.length == 2) {
            //we have exactly 2 items after splitting,
            //this indicates that there is only a channel name and value
            try {
                chName = lineItems[0];
                chValue = parseFloat(lineItems[1]);
                if (isNaN(chValue)) throw new Error("Unparsable floating point value");
            } catch (e) {
                //Error in timestamp
                log.error(path + " Could not parse value " + e);
                skip = true;
            }
        } else if (lineItems.length == 3) {
            //we have exactly 3 items after splitting,
            //this indicates that there is a timestamp, channel name and value
            try {
                if (!isNaN(parseInt(lineItems[0]))) {
                    ts = new Date(parseInt(lineItems[0]));
                } else {
                    ts = Date.parse(lineItems[0]);
                }
                chName = lineItems[1];
                chValue = parseFloat(lineItems[2]);
                if (isNaN(ts.getTime())) throw new Error("Unparsable date");
                if (isNaN(chValue)) throw new Error("Unparsable floating point value");
            } catch (e) {
                log.error(e);
                skip = true;
            }
        } else {
            //well we have got something weird, so skip it
            log.error("Got unparsable string, skipping. Faulty string is " + line);
            skip = true;
        }
        if (!skip) measurementReceived(ts, chName, chValue);
    });
}

//Execute collector and pipe results
var execCollector = function(path, sink) {
    var running = true;
    var stdout_data = [];
    log.trace("Starting " + path);

    //try-catch block catches non-executable files and errors
    try {
        var child = spawn(path);

        child.stdout.on('data', function(data) {
            (data + '').split(/\n/).forEach(function(element) {
                stdout_data.push(element);
            });
            //Collect data from STDOUT

            log.trace("STDOUT " + stdout_data);
        });

        child.stderr.on('data', function(d) {
            log.error(path + " stderr: " + d);
        });

        child.on('close', function(code, signal) {
            running = false;
            log.trace(path + " exit: " + code + "/" + signal);
            running = false;
            if (code === 0) {
                processCollectorResult(path, stdout_data);
            } else {
                log.warn(path + " closed with code " + code);
            }
        });

        //TODO: add timeout+kill. This does not work well with node.js for some reason!
        setTimeout(function() {
            if (running) {
                child.kill("SIGTERM");
                log.warn("Sleeping process,killed " + path);
            }
        }, config.process_timeout);
    } catch (e) {
        log.error(e);
    }
};

//TODO: add data buffering if net connetion is down

// Message handling routines. slug is the same as CUID = channel UID
var ibiSendMeasurement = function(slug, m) {
    var cuid = '';

    if (typeof m.value !== "number") {
        //is it a stringed number ?
        if (!isNaN(parseFloat(m.value))) {
            //Yes, replace with parsed number
            m.value = parseFloat(m.value);
        } else {
            //Only numeric data allowed currently
            log.trace("Non-numeric measurement: " + m.value);
            return;
        }
    }

    var dp = new ibisense.models.DataPoint({
        "date": m.abs_time,
        "value": m.value
    });

    log.trace("Storing " + dp.toJsonString());

    var onError = function(status) {
        log.error("Ibisense store failed with code " + status);
    };

    var onSuccess = function(status) {
        log.trace("Ibisense store OK");
    };

    var onErrorGen = function(dpoint) {
        return function(status) {
            log.error("Ibisense store failed with code " + status + " for data " + dpoint);
        }
    }

    var onSuccessGen = function(dpoint) {
        return function(status) {
            log.trace("Stored: " + dpoint);
        };
    };

    if (typeof slug !== 'undefined' && slug && dp.toJson().t && dp.toJson().v) {
        log.trace("Storing " + slug + " = " + dp.toJsonString());
        ibisense.datapoints.add(slug, [dp],
            onSuccessGen(slug + "=" + dp.toJsonString()),
            onErrorGen(slug + "=" + dp.toJsonString()),
            null);
    } else {
        log.warn("Slug not defined for " + m.nodename);
    }
};

var startCollectors = function() {
    setInterval(
        function() {
            runCollectors();
        }, 5000
    );
};

var bootstrapDelayed = function(retries) {
    setTimeout(function() {
        bootstrap(retries)
    }, 1000);
};

var bootstrap = function(retries) {

    if (!retries) process.exit(-1);
    else retries--;

    if (config.apikey && config.sensorid) {
        ibisense.setApiKey(config.apikey);
        ibisense.sensors.get(config.sensorid,
            function(sensor) {
                console.log("OK. Sensor was  previously registered. Loading channels.")
                ibisense.channels.list(config.sensorid,
                    function(channels) {
                        for (var i in channels) {
                            var ch = channels[i];
                            log.info("Data from sensor '" + ch.abbreviation() + "' will be stored to channel '" + ch.cuid() + "'");
                            channelSlugs[ch.abbreviation()] = ch.cuid();
                        }
                        startCollectors();
                    }, function(code) {
                        log.error("Error on loading channel data from Ibisense, status=" + code);
                    }
                );
            }, function(code) {
                console.log("ERROR. Sensor was not registered (" + code + ")");
                bootstrapDelayed(retries);
            });
    } else {
        exec(config.get_mac_address_command, function(error, stdout, stderr) {
            if (error !== null) {
                console.log('exec error: ' + error);
                process.exit(-1); //fatal
            }

            var serial = stdout || "unknown";
            serial = serial.replace(/^\s*/, '').replace(/\s*$/, '');
            console.log("Registering device with serial number: " + serial + ".");
            if (serial === "unknown") {
                console.log("Could not retrieve device serial number");
                process.exit(-1); //fatal
            }

            // By default, use MAC address of the RPi as the registration secret
            // This is not the most secure method, but better than nothing - now
            // you can share the cpuid to others with whom you wish to share the
            // data and they won't be able to fake your data without knowing the MAC
            // address;

            exec(config.get_cpu_number_command, function(error, stdout, stderr) {

                var secret;

                if (config.secret) {
                    secret = config.secret;
                } else {
                    secret = stdout || "";
                    secret = secret.replace(/^\s*/, '').replace(/\s*$/, '');

                    if (secret === "") {
                        console.log("Using an empty secret");
                    }
                }

                log.trace("SECRET=" + secret);
                ibisense.activation.activateUnregistered({
                        'serial': serial,
                        'secret': secret
                    },
                    function(apikey, suid) {
                        console.log("Device with serial number " + serial + " was registered in the Ibisense cloud: SUID " + suid + " API KEY: " + apikey);
                        config.apikey = apikey;
                        config.sensorid = suid;
                        ibisense.setApiKey(config.apikey);
                        fs.writeFile(config_file, JSON.stringify(config, null, 4),
                            function(err) {
                                if (err) {
                                    console.log("There has been an error saving your configuration data.");
                                    console.log(err.message);
                                    bootstrapDelayed(retries);
                                }
                                console.log("Configuration saved successfully.")
                                bootstrap(retries); //startCollectors();
                            }
                        );
                    }, function(code) {
                        console.log("There was an error registering the device in Ibisense cloud. Error code : " + code);
                        bootstrapDelayed(retries);
                    }
                );
            });
        });
    }
};

// MAIN - startup

var bootstrapRetries = 10;
bootstrap(bootstrapRetries);<commit_msg>prevent from creating tons of channels<commit_after>//
// Copyright (c) 2013, Ibisense ltd.
// see file LICENSE for licensing (BSD)
//
"use strict";

global.jQuery = require('jquery');

var sys = require('sys');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var fs = require('fs');
var ibisense = require("../lib/ibisense.api.js");
var channelSlugs = {};
var config_file = "../etc/ibideviced_config.json";

//Configuration
var config = JSON.parse(fs.readFileSync("../etc/ibideviced_config.json"));

if (!config) {
    console.log("No configuration file was found. Exiting.");
    process.exit(-1);
}

//Init Ibisense API
ibisense.setApiKey(config.apikey);

//Logging
var log4js = require("log4js");
log4js.configure({
    "appenders": [{
        "type": "file",
        "filename": config.log_dir + '/ibideviced.log',
        "maxLogSize": 1048576,
        "backups": 3,
        "category": "ibideviced"
    }],
});

var log = log4js.getLogger("ibideviced");
log.setLevel(log4js.levels.INFO);

//Debug on / off ?
//log.setLevel(log4js.levels.TRACE)

//Here are the data collecting scripts
var collectors_path = config.collectors_dir;
if (collectors_path[collectors_path.length - 1] !== '/') {
    collectors_path = collectors_path + "/";
}

//Checks if the collector file looks runnable. TBD: check file permissions for +x
var collector_runnable = function(name) {
    //TODO: add check for executable and non-directory
    //Do not run backup files
    if (name.match(/^\.$/) || name.match(/\#/) || name.match(/~$/)) {
        return false;
    } else {
        return true;
    }
}

//Run all the collectors in the directory
var runCollectors = function() {
    try {
        var collectors = fs.readdirSync(collectors_path);
    } catch (e) {
        //TODO: add exception handling
    }

    for (var c_idx in collectors) {
        var collector = collectors[c_idx];
        log.trace(collector);
        if (collector_runnable(collector)) {
            execCollector(collectors_path + collector, null);
        }
    }
};

//Look up CUID for a measurement
//Create new channel
var pending_add_ch = {};
var createChannel = function(ts, chName, chValue) {
    var ch = new ibisense.models.Channel({
        "name": "RPi sensor " + chName,
        "description": "",
        "unit": "",
        "abbreviation": chName
    });

    if (pending_add_ch[chName]) return;
    pending_add_ch[chName] = true;

    log.warn("New channel: " + ch.toJsonString());
    ibisense.channels.add(config.sensorid,
        ch,
        function(newch, status) {
            //New channel created - store CUID and repost data
            log.info("Created new channel '" + newch.cuid() + "' for sensor '" + chName + "'");
            channelSlugs[chName] = newch.cuid();
            measurementReceived(ts, chName, chValue);
        },
        function() {
            //Error
            pending_add_ch[chName] = false;
            log.error("Can't create new channel " + chName);
        });;

}

//A new measurement received event
var measurementReceived = function(ts, chName, chValue) {
    var m = {
        'abs_time': ts,
        'value': chValue
    };

    //Do we already know the Ibisense Channel ?
    if (channelSlugs[chName]) {
        //If the CUID is known, send the data to Ibisense
        ibiSendMeasurement(channelSlugs[chName], m);
    } else {
        //If CUID (Channel UID) is unknown, poll for it. If not known, create channel
        //measurementUnknownCUID(chname, m);
        log.warn("Unknown CUID for " + chName);
        createChannel(ts, chName, chValue);
    }
}

// Processes results from collectors

//Process data formats like:
// T,20

// T1,20.34 °C
// T2,34.22 °C

// V1,12.00V
// RH,34 %

// Use * to denote measurement timestamp if it is not current:
// *02:03:00,T,20
// *02:03:01,T,20

//Currently supported formats:
//No timestamp:
//  ChannelName ChannelValue
//  ChannelName,ChannelValue
//With timestamp:
//  Timestamp,ChannelName,ChannelValue
//  Timestamp ChannelName ChannelValue

var processCollectorResult = function(path, lines) {
    lines.forEach(function(line) {
        //Try to split the line by comma
        var lineItems = line.replace(/^\s+|\s+$/g, '').split(/\s+|\,/);
        var ts = new Date(),
            chName, chValue;
        var skip = false;
        log.trace("Received new measurement: " + line);
        if (lineItems.length == 2) {
            //we have exactly 2 items after splitting,
            //this indicates that there is only a channel name and value
            try {
                chName = lineItems[0];
                chValue = parseFloat(lineItems[1]);
                if (isNaN(chValue)) throw new Error("Unparsable floating point value");
            } catch (e) {
                //Error in timestamp
                log.error(path + " Could not parse value " + e);
                skip = true;
            }
        } else if (lineItems.length == 3) {
            //we have exactly 3 items after splitting,
            //this indicates that there is a timestamp, channel name and value
            try {
                if (!isNaN(parseInt(lineItems[0]))) {
                    ts = new Date(parseInt(lineItems[0]));
                } else {
                    ts = Date.parse(lineItems[0]);
                }
                chName = lineItems[1];
                chValue = parseFloat(lineItems[2]);
                if (isNaN(ts.getTime())) throw new Error("Unparsable date");
                if (isNaN(chValue)) throw new Error("Unparsable floating point value");
            } catch (e) {
                log.error(e);
                skip = true;
            }
        } else {
            //well we have got something weird, so skip it
            log.error("Got unparsable string, skipping. Faulty string is " + line);
            skip = true;
        }
        if (!skip) measurementReceived(ts, chName, chValue);
    });
}

//Execute collector and pipe results
var execCollector = function(path, sink) {
    var running = true;
    var stdout_data = [];
    log.trace("Starting " + path);

    //try-catch block catches non-executable files and errors
    try {
        var child = spawn(path);

        child.stdout.on('data', function(data) {
            (data + '').split(/\n/).forEach(function(element) {
                stdout_data.push(element);
            });
            //Collect data from STDOUT

            log.trace("STDOUT " + stdout_data);
        });

        child.stderr.on('data', function(d) {
            log.error(path + " stderr: " + d);
        });

        child.on('close', function(code, signal) {
            running = false;
            log.trace(path + " exit: " + code + "/" + signal);
            running = false;
            if (code === 0) {
                processCollectorResult(path, stdout_data);
            } else {
                log.warn(path + " closed with code " + code);
            }
        });

        //TODO: add timeout+kill. This does not work well with node.js for some reason!
        setTimeout(function() {
            if (running) {
                child.kill("SIGTERM");
                log.warn("Sleeping process,killed " + path);
            }
        }, config.process_timeout);
    } catch (e) {
        log.error(e);
    }
};

//TODO: add data buffering if net connetion is down

// Message handling routines. slug is the same as CUID = channel UID
var ibiSendMeasurement = function(slug, m) {
    var cuid = '';

    if (typeof m.value !== "number") {
        //is it a stringed number ?
        if (!isNaN(parseFloat(m.value))) {
            //Yes, replace with parsed number
            m.value = parseFloat(m.value);
        } else {
            //Only numeric data allowed currently
            log.trace("Non-numeric measurement: " + m.value);
            return;
        }
    }

    var dp = new ibisense.models.DataPoint({
        "date": m.abs_time,
        "value": m.value
    });

    log.trace("Storing " + dp.toJsonString());

    var onError = function(status) {
        log.error("Ibisense store failed with code " + status);
    };

    var onSuccess = function(status) {
        log.trace("Ibisense store OK");
    };

    var onErrorGen = function(dpoint) {
        return function(status) {
            log.error("Ibisense store failed with code " + status + " for data " + dpoint);
        }
    }

    var onSuccessGen = function(dpoint) {
        return function(status) {
            log.trace("Stored: " + dpoint);
        };
    };

    if (typeof slug !== 'undefined' && slug && dp.toJson().t && dp.toJson().v) {
        log.trace("Storing " + slug + " = " + dp.toJsonString());
        ibisense.datapoints.add(slug, [dp],
            onSuccessGen(slug + "=" + dp.toJsonString()),
            onErrorGen(slug + "=" + dp.toJsonString()),
            null);
    } else {
        log.warn("Slug not defined for " + m.nodename);
    }
};

var startCollectors = function() {
    setInterval(
        function() {
            runCollectors();
        }, 5000
    );
};

var bootstrapDelayed = function(retries) {
    setTimeout(function() {
        bootstrap(retries)
    }, 1000);
};

var bootstrap = function(retries) {

    if (!retries) process.exit(-1);
    else retries--;

    if (config.apikey && config.sensorid) {
        ibisense.setApiKey(config.apikey);
        ibisense.sensors.get(config.sensorid,
            function(sensor) {
                console.log("OK. Sensor was  previously registered. Loading channels.")
                ibisense.channels.list(config.sensorid,
                    function(channels) {
                        for (var i in channels) {
                            var ch = channels[i];
                            log.info("Data from sensor '" + ch.abbreviation() + "' will be stored to channel '" + ch.cuid() + "'");
                            channelSlugs[ch.abbreviation()] = ch.cuid();
                        }
                        startCollectors();
                    }, function(code) {
                        log.error("Error on loading channel data from Ibisense, status=" + code);
                    }
                );
            }, function(code) {
                console.log("ERROR. Sensor was not registered (" + code + ")");
                bootstrapDelayed(retries);
            });
    } else {
        exec(config.get_mac_address_command, function(error, stdout, stderr) {
            if (error !== null) {
                console.log('exec error: ' + error);
                process.exit(-1); //fatal
            }

            var serial = stdout || "unknown";
            serial = serial.replace(/^\s*/, '').replace(/\s*$/, '');
            console.log("Registering device with serial number: " + serial + ".");
            if (serial === "unknown") {
                console.log("Could not retrieve device serial number");
                process.exit(-1); //fatal
            }

            // By default, use MAC address of the RPi as the registration secret
            // This is not the most secure method, but better than nothing - now
            // you can share the cpuid to others with whom you wish to share the
            // data and they won't be able to fake your data without knowing the MAC
            // address;

            exec(config.get_cpu_number_command, function(error, stdout, stderr) {

                var secret;

                if (config.secret) {
                    secret = config.secret;
                } else {
                    secret = stdout || "";
                    secret = secret.replace(/^\s*/, '').replace(/\s*$/, '');

                    if (secret === "") {
                        console.log("Using an empty secret");
                    }
                }

                log.trace("SECRET=" + secret);
                ibisense.activation.activateUnregistered({
                        'serial': serial,
                        'secret': secret
                    },
                    function(apikey, suid) {
                        console.log("Device with serial number " + serial + " was registered in the Ibisense cloud: SUID " + suid + " API KEY: " + apikey);
                        config.apikey = apikey;
                        config.sensorid = suid;
                        ibisense.setApiKey(config.apikey);
                        fs.writeFile(config_file, JSON.stringify(config, null, 4),
                            function(err) {
                                if (err) {
                                    console.log("There has been an error saving your configuration data.");
                                    console.log(err.message);
                                    bootstrapDelayed(retries);
                                }
                                console.log("Configuration saved successfully.")
                                bootstrap(retries); //startCollectors();
                            }
                        );
                    }, function(code) {
                        console.log("There was an error registering the device in Ibisense cloud. Error code : " + code);
                        bootstrapDelayed(retries);
                    }
                );
            });
        });
    }
};

// MAIN - startup

var bootstrapRetries = 10;
bootstrap(bootstrapRetries);
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014-2016, Matteo Collina <hello@matteocollina.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
'use strict'

function buildTests (opts) {
  var builder = opts.builder
  var test = opts.test

  test('support on and emit', function (t) {
    t.plan(4)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.on('hello world', function (message, cb) {
      t.equal(e.current, 1, 'number of current messages')
      t.deepEqual(message, expected)
      t.equal(this, e)
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support multiple subscribers', function (t) {
    t.plan(3)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.on('hello world', function (message, cb) {
      t.ok(message, 'message received')
      cb()
    }, function () {
      e.on('hello world', function (message, cb) {
        t.ok(message, 'message received')
        cb()
      }, function () {
        e.emit(expected, function () {
          e.close(function () {
            t.pass('closed')
          })
        })
      })
    })
  })

  test('support multiple subscribers and unsubscribers', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    function first (message, cb) {
      t.fail('first listener should not receive any events')
      cb()
    }

    function second (message, cb) {
      t.ok(message, 'second listener must receive the message')
      cb()
      e.close(function () {
        t.pass('closed')
      })
    }

    e.on('hello world', first, function () {
      e.on('hello world', second, function () {
        e.removeListener('hello world', first, function () {
          e.emit(expected)
        })
      })
    })
  })

  test('removeListener', function (t) {
    t.plan(1)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }
    var toRemoveCalled = false

    function toRemove (message, cb) {
      toRemoveCalled = true
      cb()
    }

    e.on('hello world', function (message, cb) {
      cb()
    }, function () {
      e.on('hello world', toRemove, function () {
        e.removeListener('hello world', toRemove, function () {
          e.emit(expected, function () {
            e.close(function () {
              t.notOk(toRemoveCalled, 'the toRemove function must not be called')
            })
          })
        })
      })
    })
  })

  test('without a callback on emit and on', function (t) {
    t.plan(1)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.on('hello world', function (message, cb) {
      cb()
      e.close(function () {
        t.pass('closed')
      })
    })

    setTimeout(function () {
      e.emit(expected)
    }, 100)
  })

  test('without any listeners', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.emit(expected)
    t.equal(e.current, 0, 'reset the current messages trackers')
    e.close(function () {
      t.pass('closed')
    })
  })

  test('support one level wildcard', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello/world',
      payload: { my: 'message' }
    }

    e.on('hello/+', function (message, cb) {
      t.equal(message.topic, 'hello/world')
      cb()
    }, function () {
      // this will not be catched
      e.emit({ topic: 'hello/my/world' })

      // this will be catched
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support changing one level wildcard', function (t) {
    t.plan(2)

    var e = builder({ wildcardOne: '~' })
    var expected = {
      topic: 'hello/world',
      payload: { my: 'message' }
    }

    e.on('hello/~', function (message, cb) {
      t.equal(message.topic, 'hello/world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support deep wildcard', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello/my/world',
      payload: { my: 'message' }
    }

    e.on('hello/#', function (message, cb) {
      t.equal(message.topic, 'hello/my/world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support changing deep wildcard', function (t) {
    t.plan(2)

    var e = builder({ wildcardSome: '*' })
    var expected = {
      topic: 'hello/my/world',
      payload: { my: 'message' }
    }

    e.on('hello/*', function (message, cb) {
      t.equal(message.topic, 'hello/my/world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support changing the level separator', function (t) {
    t.plan(2)

    var e = builder({ separator: '~' })
    var expected = {
      topic: 'hello~world',
      payload: { my: 'message' }
    }

    e.on('hello~+', function (message, cb) {
      t.equal(message.topic, 'hello~world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('close support', function (t) {
    var e = builder()
    var check = false

    t.notOk(e.closed, 'must have a false closed property')

    e.close(function () {
      t.ok(check, 'must delay the close callback')
      t.ok(e.closed, 'must have a true closed property')
      t.end()
    })

    check = true
  })

  test('emit after close errors', function (t) {
    var e = builder()

    e.close(function () {
      e.emit({ topic: 'hello' }, function (err) {
        t.ok(err, 'must return an error')
        t.end()
      })
    })
  })

  test('support multiple subscribers with wildcards', function (t) {
    var e = builder()
    var expected = {
      topic: 'hello/world',
      payload: { my: 'message' }
    }
    var firstCalled = false
    var secondCalled = false

    e.on('hello/#', function (message, cb) {
      t.notOk(firstCalled, 'first subscriber must only be called once')
      firstCalled = true
      cb()
    })

    e.on('hello/+', function (message, cb) {
      t.notOk(secondCalled, 'second subscriber must only be called once')
      secondCalled = true
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.end()
        })
      })
    })
  })

  test('support multiple subscribers with wildcards (deep)', function (t) {
    var e = builder()
    var expected = {
      topic: 'hello/my/world',
      payload: { my: 'message' }
    }
    var firstCalled = false
    var secondCalled = false

    e.on('hello/#', function (message, cb) {
      t.notOk(firstCalled, 'first subscriber must only be called once')
      firstCalled = true
      cb()
    })

    e.on('hello/+/world', function (message, cb) {
      t.notOk(secondCalled, 'second subscriber must only be called once')
      secondCalled = true
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.end()
        })
      })
    })
  })
}

module.exports = buildTests
<commit_msg>Added test case for emitting buffers<commit_after>/*
 * Copyright (c) 2014-2016, Matteo Collina <hello@matteocollina.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
'use strict'

function buildTests (opts) {
  var builder = opts.builder
  var test = opts.test

  test('support on and emit', function (t) {
    t.plan(4)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.on('hello world', function (message, cb) {
      t.equal(e.current, 1, 'number of current messages')
      t.deepEqual(message, expected)
      t.equal(this, e)
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support multiple subscribers', function (t) {
    t.plan(3)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.on('hello world', function (message, cb) {
      t.ok(message, 'message received')
      cb()
    }, function () {
      e.on('hello world', function (message, cb) {
        t.ok(message, 'message received')
        cb()
      }, function () {
        e.emit(expected, function () {
          e.close(function () {
            t.pass('closed')
          })
        })
      })
    })
  })

  test('support multiple subscribers and unsubscribers', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    function first (message, cb) {
      t.fail('first listener should not receive any events')
      cb()
    }

    function second (message, cb) {
      t.ok(message, 'second listener must receive the message')
      cb()
      e.close(function () {
        t.pass('closed')
      })
    }

    e.on('hello world', first, function () {
      e.on('hello world', second, function () {
        e.removeListener('hello world', first, function () {
          e.emit(expected)
        })
      })
    })
  })

  test('removeListener', function (t) {
    t.plan(1)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }
    var toRemoveCalled = false

    function toRemove (message, cb) {
      toRemoveCalled = true
      cb()
    }

    e.on('hello world', function (message, cb) {
      cb()
    }, function () {
      e.on('hello world', toRemove, function () {
        e.removeListener('hello world', toRemove, function () {
          e.emit(expected, function () {
            e.close(function () {
              t.notOk(toRemoveCalled, 'the toRemove function must not be called')
            })
          })
        })
      })
    })
  })

  test('without a callback on emit and on', function (t) {
    t.plan(1)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.on('hello world', function (message, cb) {
      cb()
      e.close(function () {
        t.pass('closed')
      })
    })

    setTimeout(function () {
      e.emit(expected)
    }, 100)
  })

  test('without any listeners', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello world',
      payload: { my: 'message' }
    }

    e.emit(expected)
    t.equal(e.current, 0, 'reset the current messages trackers')
    e.close(function () {
      t.pass('closed')
    })
  })

  test('support one level wildcard', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello/world',
      payload: { my: 'message' }
    }

    e.on('hello/+', function (message, cb) {
      t.equal(message.topic, 'hello/world')
      cb()
    }, function () {
      // this will not be catched
      e.emit({ topic: 'hello/my/world' })

      // this will be catched
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support changing one level wildcard', function (t) {
    t.plan(2)

    var e = builder({ wildcardOne: '~' })
    var expected = {
      topic: 'hello/world',
      payload: { my: 'message' }
    }

    e.on('hello/~', function (message, cb) {
      t.equal(message.topic, 'hello/world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support deep wildcard', function (t) {
    t.plan(2)

    var e = builder()
    var expected = {
      topic: 'hello/my/world',
      payload: { my: 'message' }
    }

    e.on('hello/#', function (message, cb) {
      t.equal(message.topic, 'hello/my/world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support changing deep wildcard', function (t) {
    t.plan(2)

    var e = builder({ wildcardSome: '*' })
    var expected = {
      topic: 'hello/my/world',
      payload: { my: 'message' }
    }

    e.on('hello/*', function (message, cb) {
      t.equal(message.topic, 'hello/my/world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('support changing the level separator', function (t) {
    t.plan(2)

    var e = builder({ separator: '~' })
    var expected = {
      topic: 'hello~world',
      payload: { my: 'message' }
    }

    e.on('hello~+', function (message, cb) {
      t.equal(message.topic, 'hello~world')
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.pass('closed')
        })
      })
    })
  })

  test('close support', function (t) {
    var e = builder()
    var check = false

    t.notOk(e.closed, 'must have a false closed property')

    e.close(function () {
      t.ok(check, 'must delay the close callback')
      t.ok(e.closed, 'must have a true closed property')
      t.end()
    })

    check = true
  })

  test('emit after close errors', function (t) {
    var e = builder()

    e.close(function () {
      e.emit({ topic: 'hello' }, function (err) {
        t.ok(err, 'must return an error')
        t.end()
      })
    })
  })

  test('support multiple subscribers with wildcards', function (t) {
    var e = builder()
    var expected = {
      topic: 'hello/world',
      payload: { my: 'message' }
    }
    var firstCalled = false
    var secondCalled = false

    e.on('hello/#', function (message, cb) {
      t.notOk(firstCalled, 'first subscriber must only be called once')
      firstCalled = true
      cb()
    })

    e.on('hello/+', function (message, cb) {
      t.notOk(secondCalled, 'second subscriber must only be called once')
      secondCalled = true
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.end()
        })
      })
    })
  })

  test('support multiple subscribers with wildcards (deep)', function (t) {
    var e = builder()
    var expected = {
      topic: 'hello/my/world',
      payload: { my: 'message' }
    }
    var firstCalled = false
    var secondCalled = false

    e.on('hello/#', function (message, cb) {
      t.notOk(firstCalled, 'first subscriber must only be called once')
      firstCalled = true
      cb()
    })

    e.on('hello/+/world', function (message, cb) {
      t.notOk(secondCalled, 'second subscriber must only be called once')
      secondCalled = true
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.end()
        })
      })
    })
  })

  test('emit & receive buffers', function (t) {
    var e = builder()
    var msg = new Buffer('hello')
    var expected = {
      topic: 'hello',
      payload: msg
    }

    e.on('hello', function (message, cb) {
      t.deepEqual(msg, message.payload)
      cb()
    }, function () {
      e.emit(expected, function () {
        e.close(function () {
          t.end()
        })
      })
    })
  })
}

module.exports = buildTests
<|endoftext|>