public/javascripts/datainterfacing.js

/**
 * @module Data-Interfacing
 * @author Eric Udlis, Zander Huang
 * @description Interface with the local tempory cache and long term recording
 */

const EVENTS = require('events');
const FS = require('fs');

/** @requires module:jsons-database */
const STORED_DATA = require('../../database.json');

/** @requires module:cache */
const CACHE = require('../../cache');

/** @requires module:Recording */
const DATA_RECORDING = require('../../dataRecording');

const State = require('./State');

const PACKET_HANDLER = new EVENTS.EventEmitter();
module.exports.packetHandler = PACKET_HANDLER;

module.exports.isDataRecording = false;

/** @constant - Changes the data recording variable from the handler */
const RECORDING_EVENT = new EVENTS.EventEmitter();
module.exports.recordingEvent = RECORDING_EVENT;

// Sets that data is recording
RECORDING_EVENT.on('on', () => {
  isDataRecording = true;
  module.exports.isDataRecording = isDataRecording;
});

// Sets that data is not recording
RECORDING_EVENT.on('off', () => {
  isDataRecording = false;
  module.exports.isDataRecording = isDataRecording;
});

/**
 * Creates a cache based off of database.JSON
 */
module.exports.createCache = function createCache(name) { // eslint-disable-line no-unused-vars
  console.log('Creating cache');
  if (name === DATA_RECORDING) { // creates dataRecording cache
    try {
      let subsystemsArray = Object.keys(STORED_DATA);
      for (let i = 0; i < subsystemsArray.length; i += 1) {
        let sensorsArray = Object.keys(STORED_DATA[subsystemsArray[i]]);
        DATA_RECORDING[subsystemsArray[i]] = {};
        for (let z = 0; z < sensorsArray.length; z += 1) {
          DATA_RECORDING[subsystemsArray[i]][sensorsArray[z]] = [];
        }
      }
    } catch (error) {
      console.error('dataRecording was not found');
    }
  } else { // creates dashboard cache
    try {
      let subsystemsArray = Object.keys(STORED_DATA);
      for (let i = 0; i < subsystemsArray.length; i += 1) {
        let sensorsArray = Object.keys(STORED_DATA[subsystemsArray[i]]);
        CACHE[subsystemsArray[i]] = {};
        for (let z = 0; z < sensorsArray.length; z += 1) {
          CACHE[subsystemsArray[i]][sensorsArray[z]] = [];
        }
      }
    } catch (error) {
      console.error('cache was not found');
    }
  }
};

/**
 * Modifies packet to fill maxControllerTemp flag
 * @param {Object} input Input Packet
 * @returns {Object} The modified packet
 */
function getMaxMotorControllerTemp(input) {
  let fixedPacket = input;
  // console.log(`${input.motor.controlBoardTemp} | ${input.motor.gateDriverBoardTemp}
  // | ${input.motor.phaseAIGBTTemp}`);
  // fixedPacket.motor.maxControllerTemp = Math.max(Number(input.motor.controlBoardTemp),
  //   Number(input.motor.gateDriverBoardTemp), Number(input.phaseAIGBTTemp));

  fixedPacket.motor.maxControllerTemp = input.motor.controlBoardTemp;
  return fixedPacket;
}

/**
 * Modifies packet to fill packPowerConsumed flag
 * @param {Object} input Input Packet
 * @returns {Object} - The Modified packet
 */
function getPowerConsumed(input) {
  let fixedPacket = input;

  let power = input.battery.packCurrent * input.battery.packVoltage;
  fixedPacket.battery.packPowerConsumed = power;
  return fixedPacket;
}

/**
 * Modifies packet to fill packPowerRemaining flag
 * @param {Object} input Input packet
 * @returns {Object} - The modified packet
 */
function getPackPowerRemaining(input) {
  let fixedPacket = input;
  let ampHours = 6; // Got this value from Shelby
  let wattHours = input.battery.packVoltage * ampHours * input.battery.packSOC;
  fixedPacket.battery.packPowerRemaining = wattHours;

  return fixedPacket;
}
/**
 * Modifies packet to invert speed of motor
 * @param {Object} input Input packet
 * @returns {Object} - THe modified packet
 */
function getAbsoluteSpeed(input) {
  let fixedPacket = input;
  fixedPacket.motor.motorSpeed = -input.motor.motorSpeed;
  return fixedPacket;
}

/**
 * Calculates the LV batteries State of Charge
 * @param {Number} x - Voltage of LV Battery
 * @returns {Number} - The pack's state of charge
 */
function interpolateLVSOC(x) {
  // From LV Pack SOC spreadsheet
  let y = 1.1142 * (x ** 6) + 78.334 * (x ** 5) - 2280.5 * (x ** 4)
   + 35181 * (x ** 3) - 404340 * (x ** 2) + 1000000 * x + 3000000;
  return Number(y);
}

/**
 * Modifies packet to fill LVSOC flag
 * @param {Object} input The input packet
 * @returns {Object} - The modified packet
 */
function getLVSOC(input) { // eslint-disable-line no-unused-vars
  let fixedPacket = input;
  let x = Number(input.battery.lvVoltage);
  let lvSOC = interpolateLVSOC(x);
  fixedPacket.battery.lvSOC = lvSOC;

  return fixedPacket;
}
/**
 * Converts ambient pressure reading to gauge pressure readings
 * @param {Object} input The input packet
 * @returns {Object} - The modified packet
 */
function ambientToGauge(input) { // eslint-disable-line no-unused-vars
  let fixedPacket = input;

  fixedPacket.braking.primaryTank = input.braking.primaryTank + 14.7;
  fixedPacket.braking.primaryLine = input.braking.primaryLine + 14.7;
  fixedPacket.braking.primaryActuation = input.braking.primaryActuation + 14.7;
  fixedPacket.braking.secondaryTank = input.braking.secondaryTank + 14.7;
  fixedPacket.braking.secondaryLine = input.braking.secondaryLine + 14.7;
  fixedPacket.braking.secondaryActuation = input.braking.secondaryActuation + 14.7;

  return fixedPacket;
}

/**
 * @description Sort through the data and append the new values to their respective
 * arrays in cache.js
 * NormalizePacket -> Calculations -> [UpdateData]
 * @param {Object} dataIn The packet to process
 * @param {String} location The location you're writing to
 */
function updateData(dataIn, location) {
  const groups = Object.keys(dataIn);
  groups.forEach((group) => {
    const sensors = Object.keys(dataIn[group]);
    sensors.forEach((sensor) => {
      try {
        const input = Number(dataIn[group][sensor]);
        const target = location[group][sensor];
        let temp;
        if (group === 'braking') {
          temp = input.toFixed(0);
          // console.log(' braking');
        } else {
          temp = input.toFixed(3);
          if (sensor === 'imdStatus' && input === 0) temp = 'Off';
          if (sensor === 'imdStatus' && input === 1) temp = 'On';
          // console.log(' else');
        }
        target.push(temp);
      } catch (error) {
        // console.error(`Error: Sensor ${sensor} in ${group} not found in cache`);
      }
    });
  });
  // eslint-disable-next-line
  dataIn = null;
}
/**
 *  Any Calcuations that need to be done prior to RECORDING should be done here
 *  NormalizePacket ->  [Calculations] -> UpdateData
 * @param {Object} input Packet to process
 */
function calculate(input) {
  let fixedPacket = input;
  // Take the Max of the three motor controller temp sensors and put the max in maxControllerTemp
  try {
    // if (input.braking) fixedPacket = ambientToGauge(fixedPacket);
    if (input.battery) {
      fixedPacket = getPowerConsumed(fixedPacket);
      fixedPacket = getPackPowerRemaining(fixedPacket);
      // fixedPacket = getLVSOC(fixedPacket);
    }
    if (input.motion) {} // eslint-disable-line
    if (input.motor) {
      fixedPacket = getMaxMotorControllerTemp(fixedPacket);
      fixedPacket = getAbsoluteSpeed(fixedPacket);
    }
  } catch (err) {
    // console.error(`Calculation Error: ${err}`);
  }
  // Put the new data in the cache
  updateData(fixedPacket, CACHE);
  if (this.isDataRecording) {
    updateData(fixedPacket, DATA_RECORDING);
  }
}

/**
 * Read and remove anything from the packet that is not data
 * Any calculations that need to be done before prior to RECORDING the data should be done here
 * [NormalizePacket] -> Calcuations -> UpdateData
 * @param {Object} input - The packet to process
 */
module.exports.normalizePacket = function normalizePacket(input) {
  const { state } = input;
  let fixedPacket = input;
  // console.info('Incomming Packet:');
  // console.info(input);
  if (state) {
    State.setActiveState(state, null, true);
    delete fixedPacket.state;
  }

  if (input.battery) {
    delete fixedPacket.battery.cells;
    delete fixedPacket.battery.minCellTemp;
    delete fixedPacket.battery.avgCellTemp;
  }
  // Move packet to UpdateData
  calculate(fixedPacket);
};

/**
 * Finds subsystems in database that are renderable returns that list
 * @returns {Object} - List of renderable sensors
 */
module.exports.findRenderable = function findRenderable() {
  let renderable = STORED_DATA;
  let subsystems = Object.keys(renderable);
  subsystems.forEach((subsystem) => {
    sensors = Object.keys(renderable[subsystem]);
    sensors.forEach((sensor) => {
      let currentLocaton = renderable[subsystem][sensor];
      if (!currentLocaton.show) delete renderable[subsystem][sensor];
    });
  });
  return renderable;
};

// Exporting

/**
 * Creates the ID for the file
 */
function createID() {
  let d = new Date();
  return `${d.getMonth() + 1}-${d.getDate()}--${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`;
}

/**
 * Writes export to file
 * @param {String} name - The name of the export file
 */
function createJSON(name) {
  FS.writeFileSync(`./Exports/${name}.json`, JSON.stringify(DATA_RECORDING), (err) => {
    if (err) throw err;
    console.log(`${name}.json Created!`);
  });
}
/**
 * Creates archive file with name or id
 * @param {String} name - The name of the file to export
 */
module.exports.archiveData = function archiveData(name) {
  if (name) createJSON(name);
  else createJSON(createID());
};