Airbreaks-Low-Power/Programming/Javascript/Environment_Simulator.js
2023-12-19 18:30:36 -06:00

370 lines
21 KiB
JavaScript

const misc_functions = require('./Environment_Simulator_Functions.js');
const plotly = require('plotly')('judson.upchurch', 'dCbMwLXMmC8cdXua7NP1');
const open = require('open');
var fs = require('fs');
/***********************************************************************************************************************************************/
/**************************************************************Serial***************************************************************************/
/***********************************************************************************************************************************************/
const serialport = require("serialport");
const readline = require("@serialport/parser-readline");
const Plotly = require('plotly');
const port = new serialport("COM3", {baudRate: 115200});
const parser = port.pipe(new readline({delimiter: '\n'}));
const ROCKET_MASS = 630; // Initial mass of the rocket in grams, not including the mass of the propellant
const CD_ROCKET = 0.15; // Coefficient of drag of just the rocket without the petals or parachutes deployed
const CD_PETALS = 1.35; // Coefficient of drag of only the petals
const CD_MAIN_PARACHUTE = 0.7; // Coefficient of drag of only the main parachute
const CD_DROGUE_PARACHUTE = 0.5; // Coefficient of drag of only the drogue parachute. Assumed to be lower since it trails the main parachute
// All areas are the areas perpendicular to the flow of the air in meters squared except for the streamer. Its area is its total surface area
const AREA_ROCKET = 0.013685;
// Area data for the petals as they are deployed at specific angles
// degree servo extended. divide by 3 to get petal angle
// area of single petal (mm^2)
const petal_area_data = [
[0,15,30,45,60,75,90,105,120,135],
[0,28.541,73.092,118.21,163.458,208.747,254.045,299.334,344.603,388.35]
];
// cm of retraction
// area of parachute (m^2)
const main_parachute_area_data = [
[0, 10, 20, 30],
[0.25, 0.235, 0.21, 0.175]
];
const AREA_DROGUE_PARACHUTE = 0.05;
// Data for the F52 Motor from Aerotech
const TOTAL_IMPULSE = 78; // Newton-seconds
// time(s)
// thrust(N)
// mass remaining(g)
const F52_motor_data = [
[0,0.012,0.033,0.056,0.097,0.115,0.13,0.153,0.168,0.182,0.206,0.238,0.258,0.314,0.39,0.428,0.501,0.565,0.688,0.749,0.837,0.901,0.971,1.088,1.144,1.173,1.222,1.275,1.339,1.389,1.42],
[0,46.899,61.778,69.441,73.483,76.636,74.381,74.82,78.422,78.94,77.963,77.504,73.892,72.974,72.046,70.679,65.699,62.975,58.874,56.15,52.517,49.793,46.161,39.365,34.386,29.417,20.376,13.151,5.461,1.838,0],
[36.6,36.4588,35.8863,35.1292,33.336592,32.9814,32.4131,31.5523,30.9757,30.423,29.4783,28.2303,27.4707,25.4076,22.6428,21.2822,18.7848,16.719,12.9593,11.1992,8.80035,7.15779,5.47285,2.96266,1.92661,1.46246,0.850406,0.404654,0.105843,0.0142932,0]
];
/***********************************************************************************************************************************************/
/************************************************************Simulation*************************************************************************/
/***********************************************************************************************************************************************/
// Global variable to keep track of the actual data for the rocket so the simulator can provide real data
const actual_rocket_data = {
servo_angle: 0, // Servo angle (deg). Divide by 3 to get petal angle
parachute_retract_distance: 0.0, // Main parachute retract distance (cm)
parachute_retract_speed: 0.0, // The speed that the parachute is retracting (cm/s)
stage: 0, // Stage of the rocket.
//0 = on launch rod, 1 = after motor ignition
//2 = after self propelled flight, 3 = after motor burnout
//4 = after apogee, 5 = terminal velocity
//6 = right above the ground, 7 = after ground hit
mass: ROCKET_MASS, // Mass (g)
altitude: 0.0, // Altitude (m)
position: {
x: 0.0,
y: 0.0,
z: 0.0
},
velocity: {
x: 0.0,
y: 0.0,
z: 0.0
},
accel: {
x: 0.0,
y: 0.0,
z: 0.0
},
flight_time: 0.0, // Flight time (s)
engine_thrust: 0.0, // Engine thrust (N)
rocket_drag: 0.0, // Rocket drag (N)
petal_drag: 25.0, // Petal drag (N)
main_parachute_drag: 0.0, // Main parachute drag (N)
drogue_parachute_drag: 0.0 // Drogue parachute drag (N)
};
// For sending to Arduino
var next_arduino_transmission = 0.0;
const ARDUINO_TRANSMISSION_FREQUENCY = 100; // Times per second
const REAL_WORLD_TIME_FACTOR = 0.1; // 1 means real time, 2 means 1 second in simulation = 2 seconds in real life, etc
// Continuously cycling loop
const dt = 0.001; // Number of "seconds" each loop lasts, not tied to real world timing
var force_break = false; // In case we need to stop the while loop
var last_nanoseconds = process.hrtime()[0] * 1000000000 + process.hrtime()[1];
var now_nanoseconds = process.hrtime()[0] * 1000000000 + process.hrtime()[1];
var flight_computer_ready = false;
// For Plotting
var complete_actual_flight_data = [];
var complete_flight_computer_calculated_data = [];
var next_flight_report = 0.0;
const FLIGHT_REPORT_FREQUENCY = 100;
//Environmental and simulation factors
const AIR_DENSITY = 1.225; // kg/m^3
const ALTIMETER_ERROR = 0.5; // Percent of error randomly applied to the calculated then sent altimeter readings
const ACCELEROMETER_ERROR = 5; // Percent of error randomly applied to the calculated then sent accelerometer readings
const WIRE_REEL_RADIUS = 0.75; // Radius of the wire real in cm
const MAX_SERVO_RPM = 100; // Max rpm of the servo motor for the parachute retractor
// We have it set up so that the simulator, this code, only runs right after the arduino has replied a message back
parser.on('data', function(line) {
console.log("Received: " + line);
if (!flight_computer_ready) {
if (line.trim().substring(0,1) == "S") { // Flight computer sent an "S" so it is ready
flight_computer_ready = true;
port.write("S"); // Acknowledge that we have started as well
console.log("Sent: S");
console.log();
return;
}
} // Flight computer has already said that it is ready
if (actual_rocket_data.stage >= 1) { // We have ignited
// This is computing the data until the next transmission time
while (actual_rocket_data.flight_time < next_arduino_transmission) {
now_nanoseconds = process.hrtime()[0] * 1000000000 + process.hrtime()[1];
// If real-time to iterate again
if (now_nanoseconds - last_nanoseconds >= (dt*1000000000)*REAL_WORLD_TIME_FACTOR) {
last_nanoseconds = now_nanoseconds;
iterate_data();
if (actual_rocket_data.stage == 7) { // If we have reached the ground
port.write("DONE!");
console.log("Sent: DONE!");
misc_functions.plot_data(complete_actual_flight_data, complete_flight_computer_calculated_data, plotly, open, fs);
misc_functions.save_simulated_servo_data(complete_actual_flight_data, fs);
break;
}
}
}
// This is grabbing all of the data sent back from the Arduino in order to plot
var rec_msg = line.trim();
var obj2 = new Object();
obj2 = {
flight_time: rec_msg.substring(0, rec_msg.indexOf("A")),
stage: rec_msg.substring(rec_msg.indexOf("A")+1, rec_msg.indexOf("B")),
predicted_apogee: rec_msg.substring(rec_msg.indexOf("B")+1,rec_msg.indexOf("C")),
servo_angle: rec_msg.substring(rec_msg.indexOf("C")+1,rec_msg.indexOf("D")),
main_parachute_retract_speed: rec_msg.substring(rec_msg.indexOf("D")+1,rec_msg.indexOf("E")),
main_parachute_retract_distance: actual_rocket_data.parachute_retract_distance,
mass: rec_msg.substring(rec_msg.indexOf("E")+1,rec_msg.indexOf("F")),
altitude: rec_msg.substring(rec_msg.indexOf("F")+1,rec_msg.indexOf("G")),
integration_position: {
x: rec_msg.substring(rec_msg.indexOf("G")+1,rec_msg.indexOf("H")),
y: rec_msg.substring(rec_msg.indexOf("H")+1,rec_msg.indexOf("I")),
z: rec_msg.substring(rec_msg.indexOf("I")+1,rec_msg.indexOf("J")),
},
integration_velocity: {
x: rec_msg.substring(rec_msg.indexOf("J")+1,rec_msg.indexOf("K")),
y: rec_msg.substring(rec_msg.indexOf("K")+1,rec_msg.indexOf("L")),
z: rec_msg.substring(rec_msg.indexOf("L")+1,rec_msg.indexOf("M")),
},
accel: {
x: rec_msg.substring(rec_msg.indexOf("M")+1,rec_msg.indexOf("N")),
y: rec_msg.substring(rec_msg.indexOf("N")+1,rec_msg.indexOf("O")),
z: rec_msg.substring(rec_msg.indexOf("O")+1,rec_msg.indexOf("P")),
},
total_drag: rec_msg.substring(rec_msg.indexOf("P")+1,rec_msg.indexOf("Q")),
predicted_time_of_flight: rec_msg.substring(rec_msg.indexOf("Q")+1,rec_msg.indexOf("R")),
};
if (!isNaN(obj2.servo_angle)) {
actual_rocket_data.servo_angle = obj2.servo_angle;
}
if (!isNaN(obj2.main_parachute_retract_speed)) {
actual_rocket_data.parachute_retract_speed = obj2.main_parachute_retract_speed;
}
complete_flight_computer_calculated_data.push(obj2);
// Sending new data
misc_functions.send_data(actual_rocket_data, ALTIMETER_ERROR, ACCELEROMETER_ERROR, port);
next_arduino_transmission += 1/ARDUINO_TRANSMISSION_FREQUENCY; // Setting the next time to send data to the Arduino
} else { // If there has not been ignition
if (line.trim().substring(0,1) == "I") { // Flight computer letting us know that there was ignition
port.write("I"); // Acknowledge that we have received ignition
console.log("Sent: I");
actual_rocket_data.stage = 1; // Move up to the correct stage
}
}
});
function iterate_data() {
// Appending the current actual flight data to an array to later plot
if (actual_rocket_data.flight_time >= next_flight_report) {
var obj = new Object();
obj = {
flight_time: next_flight_report,
altitude: actual_rocket_data.altitude,
velocity_x: actual_rocket_data.velocity.x,
velocity_y: actual_rocket_data.velocity.y,
velocity_z: actual_rocket_data.velocity.z,
accel_x: actual_rocket_data.accel.x,
accel_y: actual_rocket_data.accel.y,
accel_z: actual_rocket_data.accel.z,
stage: actual_rocket_data.stage,
servo_angle: actual_rocket_data.servo_angle,
parachute_retraction_distance: actual_rocket_data.parachute_retract_distance,
parachute_retraction_speed: actual_rocket_data.parachute_retract_speed,
mass: actual_rocket_data.mass,
engine_thrust: actual_rocket_data.engine_thrust,
rocket_drag: actual_rocket_data.rocket_drag,
petal_drag: actual_rocket_data.petal_drag,
main_parachute_drag: actual_rocket_data.main_parachute_drag,
drogue_parachute_drag: actual_rocket_data.drogue_parachute_drag
};
complete_actual_flight_data.push(obj);
next_flight_report += 1 / FLIGHT_REPORT_FREQUENCY;
}
// Calculate the new acceleration of the rocket
var net_forces = 0;
switch (actual_rocket_data.stage) {
case 1: // Still on the launch rod with motor ignition
net_forces = 0; // Redundant
// Check to see if we are now under self-propelled flight, aka, engine thrust > weight, aka liftoff
// Gravity
net_forces += -0.00981*(actual_rocket_data.mass);
// Rocket thrust
actual_rocket_data.engine_thrust = misc_functions.interpolate_data(F52_motor_data[0], F52_motor_data[1], 31, actual_rocket_data.flight_time);
net_forces += actual_rocket_data.engine_thrust;
// No need to check the drag because a non-zero velocity is required before achieving drag
// Calculate acceleration
actual_rocket_data.accel.z = (net_forces) / ((actual_rocket_data.mass)*0.001);
// Iterate the flight time
actual_rocket_data.flight_time += dt;
// Checking when to move to next stage
;
if (actual_rocket_data.accel.z > 0.001) { // We have liftoff
actual_rocket_data.stage = 2;
} else {
//actual_rocket_data.accel.z = 0;
}
break;
case 2: // No longer supported by the launch rod, self-propelled flight, after liftoff
// Gravity
net_forces += -0.00981*(actual_rocket_data.mass);
// Rocket thrust
actual_rocket_data.engine_thrust = misc_functions.interpolate_data(F52_motor_data[0], F52_motor_data[1], 31, actual_rocket_data.flight_time);
net_forces += actual_rocket_data.engine_thrust;
// Rocket body drag
actual_rocket_data.rocket_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_ROCKET, AREA_ROCKET, actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.rocket_drag;
// Petal drag. Multiply by 0.000003 because the area is in mm^2 and there are 3 petals total
actual_rocket_data.petal_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*0.000003*misc_functions.calculate_drag(CD_PETALS, misc_functions.interpolate_data(petal_area_data[0], petal_area_data[1], 10, actual_rocket_data.servo_angle), actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.petal_drag;
// Calculate acceleration
actual_rocket_data.accel.z = (net_forces) / ((actual_rocket_data.mass)*0.001);
// Iterate the flight time
actual_rocket_data.flight_time += dt;
// Checking when to move to next stage
if (actual_rocket_data.flight_time >= F52_motor_data[0][30]) { // We have motor burnout
actual_rocket_data.stage = 3;
}
break;
case 3: // After motor burnout has occurred
// Gravity
net_forces += -0.00981*(actual_rocket_data.mass);
// Rocket body drag
actual_rocket_data.rocket_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_ROCKET, AREA_ROCKET, actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.rocket_drag;
// Petal drag. Multiply by 0.000003 because the area is in mm^2 and there are 3 petals total
actual_rocket_data.petal_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*0.000003*misc_functions.calculate_drag(CD_PETALS, misc_functions.interpolate_data(petal_area_data[0], petal_area_data[1], 10, actual_rocket_data.servo_angle), actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.petal_drag;
// Calculate acceleration
actual_rocket_data.accel.z = (net_forces) / ((actual_rocket_data.mass)*0.001);
// Iterate the flight time
actual_rocket_data.flight_time += dt;
// Checking when to move to next stage
if (actual_rocket_data.accel.z <= -5) {
if (actual_rocket_data.velocity.z >= -3 && actual_rocket_data.velocity.z <= 3) {
actual_rocket_data.stage = 4;
}
}
break;
case 4: // After apogee, assume parachute release at apogee
// Gravity
net_forces += -0.00981*(actual_rocket_data.mass);
// Rocket body drag
actual_rocket_data.rocket_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_ROCKET, AREA_ROCKET, actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.rocket_drag;
// Petal drag. Multiply by 0.000003 because the area is in mm^2 and there are 3 petals total
actual_rocket_data.petal_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*0.000003*misc_functions.calculate_drag(CD_PETALS, misc_functions.interpolate_data(petal_area_data[0], petal_area_data[1], 10, actual_rocket_data.servo_angle), actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.petal_drag;
// Drogue parachute drag which should be relatively constant besides changes in velocity because it stays the same size
actual_rocket_data.drogue_parachute_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_DROGUE_PARACHUTE, AREA_DROGUE_PARACHUTE, actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.drogue_parachute_drag;
// Main parachute drag which changes due to its changing size
actual_rocket_data.main_parachute_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_MAIN_PARACHUTE, misc_functions.interpolate_data(main_parachute_area_data[0], main_parachute_area_data[1], 2, actual_rocket_data.parachute_retract_distance), actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.main_parachute_drag;
// Calculate acceleration
actual_rocket_data.accel.z = (net_forces) / ((actual_rocket_data.mass)*0.001);
// Iterate the flight time
actual_rocket_data.flight_time += dt;
// Checking when to move to next stage
if (actual_rocket_data.accel.z >= -.15 && actual_rocket_data.accel.z <= 0.15) {
actual_rocket_data.stage = 5;
}
break;
case 5: // After reaching normal terminal velocity from the parachutes
// Gravity
net_forces += -0.00981*(actual_rocket_data.mass);
// Rocket body drag
actual_rocket_data.rocket_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_ROCKET, AREA_ROCKET, actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.rocket_drag;
// Petal drag. Multiply by 0.000003 because the area is in mm^2 and there are 3 petals total
actual_rocket_data.petal_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*0.000003*misc_functions.calculate_drag(CD_PETALS, misc_functions.interpolate_data(petal_area_data[0], petal_area_data[1], 10, actual_rocket_data.servo_angle), actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.petal_drag;
// Drogue parachute drag which should be relatively constant besides changes in velocity because it stays the same size
actual_rocket_data.drogue_parachute_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_DROGUE_PARACHUTE, AREA_DROGUE_PARACHUTE, actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.drogue_parachute_drag;
// Main parachute drag which changes due to its changing size
actual_rocket_data.parachute_retract_distance += 6.28318*WIRE_REEL_RADIUS*((MAX_SERVO_RPM/60.0)*(actual_rocket_data.parachute_retract_speed/180))*dt;
//console.log(actual_rocket_data.parachute_retract_distance);
actual_rocket_data.main_parachute_drag = -1*misc_functions.sign(actual_rocket_data.velocity.z)*misc_functions.calculate_drag(CD_MAIN_PARACHUTE, misc_functions.interpolate_data(main_parachute_area_data[0], main_parachute_area_data[1], 4, actual_rocket_data.parachute_retract_distance), actual_rocket_data.velocity.z, AIR_DENSITY);
net_forces += actual_rocket_data.main_parachute_drag;
//console.log(misc_functions.interpolate_data(main_parachute_area_data[0], main_parachute_area_data[1], 4, actual_rocket_data.parachute_retract_distance));
//console.log(actual_rocket_data.main_parachute_drag);
// Calculate acceleration
actual_rocket_data.accel.z = (net_forces) / ((actual_rocket_data.mass)*0.001);
// Iterate the flight time
actual_rocket_data.flight_time += dt;
// Checking when to move to next stage
if (actual_rocket_data.altitude <= 0) { // Within 3 meters of the ground
//console.table(complete_actual_flight_data);
actual_rocket_data.stage = 6;
}
break;
case 6:
if (actual_rocket_data.altitude <= 0) {
actual_rocket_data.stage = 7;
}
break;
case 7: // After the rocket has reached the ground
//console.table(complete_actual_flight_data);
//console.table(complete_flight_computer_calculated_data)
//misc_functions.plot_data(complete_actual_flight_data, complete_flight_computer_calculated_data, plotly, open, fs);
break;
}
// Integrate the acceleration for a new velocity
actual_rocket_data.velocity.z += actual_rocket_data.accel.z * dt;
// Integrate velocity for a new altitude
actual_rocket_data.altitude += actual_rocket_data.velocity.z * dt;
}
// Cannot exit immediately because it takes some time to make the graphs
//process.exit();