package net.sf.openrocket.simulation.listeners.example; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.MathUtil; /** * An example listener that applies a PI-controller to adjust the cant of fins * named "CONTROL" to stop the rocket from rolling. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class RollControlListener extends AbstractSimulationListener { // Name of control fin set private static final String CONTROL_FIN_NAME = "CONTROL"; // Define custom flight data type private static final FlightDataType FIN_CANT_TYPE = FlightDataType.getType("Control fin cant", "\u03B1fc", UnitGroup.UNITS_ANGLE); // Simulation time at which PID controller is activated private static final double START_TIME = 0.5; // Desired roll rate (rad/sec) private static final double SETPOINT = 0.0; // Maximum control fin turn rate (rad/sec) private static final double TURNRATE = 10 * Math.PI / 180; // Maximum control fin angle (rad) private static final double MAX_ANGLE = 15 * Math.PI / 180; /* * PID parameters * * At M=0.3 KP oscillation threshold between 0.35 and 0.4. Good KI=3 * At M=0.6 KP oscillation threshold between 0.07 and 0.08 Good KI=2 * At M=0.9 KP oscillation threshold between 0.013 and 0.014 Good KI=0.5 */ private static final double KP = 0.007; private static final double KI = 0.2; private double rollrate; private double prevTime = 0; private double intState = 0; private double finPosition = 0; @Override public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) { // Store the current roll rate for later use rollrate = flightConditions.getRollRate(); return null; } @Override public void postStep(SimulationStatus status) throws SimulationException { // Activate PID controller only after a specific time if (status.getSimulationTime() < START_TIME) { prevTime = status.getSimulationTime(); return; } // Find the fin set named CONTROL FinSet finset = null; for (RocketComponent c : status.getConfiguration()) { if ((c instanceof FinSet) && (c.getName().equals(CONTROL_FIN_NAME))) { finset = (FinSet) c; break; } } if (finset == null) { throw new SimulationException("A fin set with name '" + CONTROL_FIN_NAME + "' was not found"); } // Determine time step double deltaT = status.getSimulationTime() - prevTime; prevTime = status.getSimulationTime(); // PID controller double error = SETPOINT - rollrate; double p = KP * error; intState += error * deltaT; double i = KI * intState; double value = p + i; // Clamp the fin angle between -MAX_ANGLE and MAX_ANGLE if (Math.abs(value) > MAX_ANGLE) { System.err.printf("Attempting to set angle %.1f at t=%.3f, clamping.\n", value * 180 / Math.PI, status.getSimulationTime()); value = MathUtil.clamp(value, -MAX_ANGLE, MAX_ANGLE); } // Limit the fin turn rate if (finPosition < value) { finPosition = Math.min(finPosition + TURNRATE * deltaT, value); } else { finPosition = Math.max(finPosition - TURNRATE * deltaT, value); } // Set the control fin cant and store the data finset.setCantAngle(finPosition); status.getFlightData().setValue(FIN_CANT_TYPE, finPosition); } }