/**
* @author Serge Babayan
* @module AircraftStatus
* @listens models/TelemetryData:data_received
* @requires models/TelemetryData
* @requires StatusManager
* @requires util/Logger
* @requires util/Validator
* @requires util/Bitmask
* @requires models/Commands
* @requires config/picpilot-config
* @copyright Waterloo Aerial Robotics Group 2016
* @licence https://raw.githubusercontent.com/UWARG/WARG-Ground-Station/master/LICENSE
* @description Keeps track of the current state of the autopilot, and informs the StatusManger about any important
* changes
* @see http://docs.uwarg.com/picpilot/datalink/
*/
var TelemetryData = require('./models/TelemetryData');
var StatusManager = require('./StatusManager');
var Bitmask = require('./util/Bitmask');
var Validator = require('./util/Validator');
var Logger = require('./util/Logger');
var picpilot_config = require('../config/picpilot-config');
var Commands = require('./models/Commands');
var AircraftStatus = function () {
/**
* @var following_path {boolean} Whether the aircraft is currently following a path
*/
this.following_path = false;
/**
* Information on the current autopilot status
* @var autopilot_status
* @type {{initializing: boolean, running: boolean, armed: boolean, killModeWarning: boolean, killModeActive: boolean}}
*/
this.autopilot_status = {
initializing: false, //Whether the aircraft is initializing
running: false, //Whether the aircraft is in its main loop
armed: false, //Whether the aircraft is armed
killModeWarning: false, //Whether the aircraft is about to go into kill mode
killModeActive: false //Whether the aircraft is currently in kill mode
};
/**
* Whether the autopilot is on or off
* @var manualMode
* @type {boolean}
*/
this.manualMode = false;
/**
* Current xbee connection status
* @var xbee
* @type {{status: boolean, timeSinceLost: null}}
*/
this.xbee = {//has not been implemented yet from the picpilots side
status: false,
timeSinceLost: null
};
/**
* Current gps connection status
* var gps
* @type {{status: boolean, timeSinceLost: null}}
*/
this.gps = {
status: false,
timeSinceLost: null
};
/**
* Current UHF connection status
* @var uhf
* @type {{status: boolean, timeSinceLost: null}}
*/
this.uhf = {
status: false,
timeSinceLost: null
};
/**
* What the last received error code was
* @type {null | number}
*/
this.pastErrorCode = null;
/**
* Startup errors that occurred on the autopilot
* @var startup_errors
* @type {{POWER_ON_RESET: boolean, BROWN_OUT_RESET: boolean, IDLE_MODE_RESET: boolean, SLEEP_MODE_RESET: boolean, SOFTWARE_WATCH_DOG_RESET: boolean, SOFTWARE_RESET: boolean, EXTERNAL_RESET: boolean, VOLTAGE_REGULATOR_RESET: boolean, ILLEGAL_OPCODE_RESET: boolean, TRAP_RESET: boolean}}
*/
this.startup_errors = {
POWER_ON_RESET: false,
BROWN_OUT_RESET: false,
IDLE_MODE_RESET: false,
SLEEP_MODE_RESET: false,
SOFTWARE_WATCH_DOG_RESET: false,
SOFTWARE_RESET: false,
EXTERNAL_RESET: false,
VOLTAGE_REGULATOR_RESET: false,
ILLEGAL_OPCODE_RESET: false,
TRAP_RESET: false
};
/**
* For keeping track and clearing the interval function when sending heartbeats to the picpilot
* @var heartBeatInterval
* @type {number}
* @private
*/
var heartBeatInterval = 5;
/**
* Starts sending heartbeats at the rate as specified in the picpilot config. Will clear and restart it if called multiple times
* @function startHeartbeat
*/
this.startHeartbeat = function () {
if (heartBeatInterval) {
clearInterval(heartBeatInterval);
}
heartBeatInterval = setInterval(function () {
Commands.sendHeartbeat();
}.bind(this), picpilot_config.get('heart_beat_timeout'));
}.bind(this);
/**
* @function telemetryCallback
* @private
* @param data {Object}
* @param data.startup_error_codes {number | null}
* @param data.gps_status {number | null}
* @param data.wireless_connection {number | null}
* @param data.autopilot_active {number | null}
* @param data.path_following {number | null}
*/
var telemetryCallback = function (data) {
checkErrorCodes(data.startup_error_codes);
checkGPS(data.gps_status);
checkUHFStatus(data.wireless_connection);
checkManualMode(data.wireless_connection);
checkPlaneStatus(data.autopilot_active);
checkPathFollowing(data.path_following);
};
TelemetryData.on('data_received', telemetryCallback);
/**
* Checks whether the plane is currently following a path
* @param status {number | null}
*/
var checkPathFollowing = function (status) {
if (typeof status !== 'undefined' || status !== null) {
this.following_path = (Number(status) === 1);
}
}.bind(this);
/**
* Checks the current autopilot status based on telemetry data
* @param number {number | null}
*/
var checkPlaneStatus = function (number) {
if (!Validator.isValidNumber(number)) {
Logger.warn('Invalid value for autopilot_active received. Value: ' + number);
}
else {
number = Number(number);
this.initializing = (number === 0);
if (number === 1) { //only set armed to false if the number is ONLY 1
this.armed = false;
}
this.armed = (number === 2);
this.running = (number === 3);
this.killModeWarning = (number === 4);
this.killModeActive = (number === 5);
StatusManager.setStatusCode('AIRCRAFT_INITIALIZE', this.initializing);
StatusManager.setStatusCode('AIRCRAFT_UNARMED', !this.armed);
StatusManager.setStatusCode('AIRCRAFT_ARMED', this.armed);
StatusManager.setStatusCode('AIRCRAFT_RUNNING', this.running);
StatusManager.setStatusCode('AIRCRAFT_KILLMODE_WARNING', this.killModeWarning);
StatusManager.setStatusCode('AIRCRAFT_KILLMODE', this.killModeActive);
}
}.bind(this);
var checkUHFStatus = function (data) {
var number = Number(data);
if (!Validator.isInteger(number)) {
Logger.warn('Invalid value for wireless_connection received. Value: ' + data);
}
else {
var bitmask = new Bitmask(number);
this.uhf.status = bitmask.getBit(1);
if (this.uhf.status) { //has been turned to true
this.uhf.timeSinceLost = null;
}
else { //has been turned to false
this.uhf.timeSinceLost = Date.now();
}
StatusManager.setStatusCode('UHF_LOST', !this.uhf.status);
}
}.bind(this);
var checkManualMode = function (data) {
var number = Number(data);
if (!Validator.isInteger(number)) {
Logger.warn('Invalid value for wireless_connection received. Value: ' + data);
}
else {
var bitmask = new Bitmask(number);
this.manualMode = !bitmask.getBit(0);
StatusManager.setStatusCode('MANUAL_MODE', this.manualMode);
}
}.bind(this);
var checkGPS = function (data) {
var number = Number(data);
if (!Validator.isValidNumber(number)) {
Logger.warn('Invalid GPS status received. Status: ' + data);
}
var connection_status = ((number & 0xf0) >> 4) > 0; // if theres at least 1 fix
if (connection_status !== this.gps.status) { //if its a different status
this.gps.status = connection_status;
StatusManager.setStatusCode('GPS_LOST', !this.gps.status);
if (this.gps.status === false) { //if it was set to false, start the timer
this.gps.timeSinceLost = Date.now();
}
else {
this.gps.timeSinceLost = null;
}
}
}.bind(this);
var checkErrorCodes = function (startup_error_codes) {
var dataNumber = Number(startup_error_codes);
if (!Validator.isInteger(dataNumber)) {
Logger.warn('Invalid data value for startup_error_codes received. Value : ' + startup_error_codes);
}
else if (this.pastErrorCode !== dataNumber) { //if we got an error code value thats different from last time
var error_codes = new Bitmask(dataNumber);
this.pastErrorCode = dataNumber;
this.startup_errors.POWER_ON_RESET = error_codes.getBit(0);
this.startup_errors.BROWN_OUT_RESET = error_codes.getBit(1);
this.startup_errors.IDLE_MODE_RESET = error_codes.getBit(2);
this.startup_errors.SLEEP_MODE_RESET = error_codes.getBit(3);
this.startup_errors.SOFTWARE_WATCH_DOG_RESET = error_codes.getBit(4);
this.startup_errors.SOFTWARE_RESET = error_codes.getBit(5);
this.startup_errors.EXTERNAL_RESET = error_codes.getBit(6);
this.startup_errors.VOLTAGE_REGULATOR_RESET = error_codes.getBit(7);
this.startup_errors.ILLEGAL_OPCODE_RESET = error_codes.getBit(8);
this.startup_errors.TRAP_RESET = error_codes.getBit(9);
StatusManager.setStatusCode('AIRCRAFT_ERROR_POWER_ON_RESET', this.startup_errors.POWER_ON_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_BROWN_OUT_RESET', this.startup_errors.BROWN_OUT_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_IDLE_MODE_RESET', this.startup_errors.IDLE_MODE_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_SLEEP_MODE_RESET', this.startup_errors.SLEEP_MODE_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_SOFTWARE_WATCH_DOG_RESET', this.startup_errors.SOFTWARE_WATCH_DOG_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_SOFTWARE_RESET', this.startup_errors.SOFTWARE_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_EXTERNAL_RESET', this.startup_errors.EXTERNAL_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_VOLTAGE_REGULATOR_RESET', this.startup_errors.VOLTAGE_REGULATOR_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_ILLEGAL_OPCODE_RESET', this.startup_errors.ILLEGAL_OPCODE_RESET);
StatusManager.setStatusCode('AIRCRAFT_ERROR_TRAP_RESET', this.startup_errors.TRAP_RESET);
}
}.bind(this)
};
module.exports = new AircraftStatus();