/**
* @author Serge Babayan
* @module util/PacketParser
* @requires util/Logger
* @requires util/Validator
* @requires models/PacketTypes
* @requires underscore
* @copyright Waterloo Aerial Robotics Group 2016
* @licence https://raw.githubusercontent.com/UWARG/WARG-Ground-Station/master/LICENSE
* @description Parses data received from the data relay into a usable format for the ground station's event emitters
* @see http://docs.uwarg.com/picpilot/datalink/
*/
var PacketTypes = require('../models/PacketTypes');
var Logger = require('./Logger');
var Validator = require('./Validator');
var _ = require('underscore');
var PacketParser = {
/**
* Compares the received headers to that of the headers in the PacketTypes module.
* Warns the user if there are extra or missing headers
* @function checkForMissingHeaders
* @param {Array} headers_array
*/
checkForMissingHeaders: function (headers_array) {
var expected_headers = {};
//create a list of the headers we expect to receive and set their value to 0
_.each(PacketTypes, function (packet_headers) {
_.each(packet_headers, function (validator, header_name) {
expected_headers[header_name] = 0;
})
});
_.each(headers_array, function (header_name) {
//if we receive a header that wasn't listed in the PacketTypes model
if (expected_headers[header_name] === undefined) {
Logger.warn('An unexpected header was received from the data relay. Header: ' + header_name);
}
else {
expected_headers[header_name]++;
}
});
_.each(expected_headers, function (count, header_name) {
if (count === 0) {
Logger.warn('Did not receive an expected header! Header: ' + header_name);
}
else if (count > 1) {
Logger.warn('Header: ' + header_name + ' was received more than once! Times: ' + count);
}
});
},
/**
* Converts a string of data into a packet type object, to be emitted by the TelemetryData module.
* Uses the PacketTypes module in order to sort the headers into the appropriate packet types as well
* as to perform the correct validations on each one
* @function parseData
* @param data {string} A stringified version of the data (split by commas)
* @param headers_array {Array} An array of the headers in the same order as the headers
* @returns {Object} A hash indexed by the packet type name, with the values being another hash in a format of header_name => data_value
* @throws Error if the specified validation function doesn't exist
* @example <caption> Given <code>'34, 89.654, ...'</code> as data and <code>['heading', 'lon', ...]</code> as headers, will return: </caption>
* {
* aircraft_position: {
* heading: 34,
* lon: 89.654
* ...
* },
* aircraft_orientation: {...},
* aircraft_gains: {...}
* ...
* }
*/
parseData: function (data, headers_array) {
if(!data || !headers_array){
return {};
}
var data_array = data.split(",").map(function(data){
return data.trim();
});
var sorted_data = {};
var current_state = {};
//warn the user if we receive an improper number of data
if (data_array.length !== headers_array.length) {
Logger.error('Number of data headers doesn\'t match the number of data! Length of data: ' + data_array.length + ' Length of headers: ' + headers_array.length);
}
//creates a current_state object, which is a hash with the keys being the header name, and the value being the data for that header
for (var i = 0; i < headers_array.length; i++) {
current_state[headers_array[i]] = data_array[i];
}
_.each(PacketTypes, function (packet_headers, packet_type_name) {
var packet_data = {};
_.each(packet_headers, function (validators, header_name) {
//if it is null, that means the data relay intentionally didn't send us the data
if (current_state[header_name] === '') {
packet_data[header_name] = null;
}
//warn the user if we didn't receive a piece of data (happens if we don't receive enough data)
else if (current_state[header_name] === undefined) {
Logger.error('Parsing Error. Value for header ' + header_name + ' not received');
packet_data[header_name] = null;
}
//otherwise if we received a piece of data, perform the necessary validations on it and convert it to a number
else {
if (validators === null) {
packet_data[header_name] = Number(current_state[header_name]);
}
else if (typeof validators === 'string') { //if its only a single validation function
if (typeof Validator[validators] !== 'function') {
throw new Error('Validator function ' + validators + ' does not exist!');
}
else if (Validator[validators](current_state[header_name])) {
packet_data[header_name] = Number(current_state[header_name]);
}
else {
Logger.warn('Validation failed for ' + header_name + '. Value: ' + current_state[header_name]);
packet_data[header_name] = null;
}
}
else { //if its in array and there are multiple validation functions
var failed = false;
for (var i = 0; i < validators.length; i++) {
if (typeof Validator[validators[i]] !== 'function') {
throw new Error('Validator function ' + validators[i] + ' does not exist!');
}
else if (!Validator[validators[i]](current_state[header_name])) {
failed = true;
}
}
if (failed) {
Logger.warn('Validation failed for ' + header_name + '. Value: ' + current_state[header_name]);
packet_data[header_name] = null;
} else {
packet_data[header_name] = Number(current_state[header_name]);
}
}
}
});
sorted_data[packet_type_name] = packet_data;
});
return sorted_data;
}
};
module.exports = PacketParser;