370 lines
21 KiB
JavaScript
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();
|