package org.cowboycoders.turbotrainers.bushido.brake; import org.cowboycoders.turbotrainers.Mode; import org.cowboycoders.turbotrainers.PowerModel; import org.fluxoid.utils.Conversions; import org.fluxoid.utils.FixedPeriodUpdater; import org.fluxoid.utils.SimpleCsvLogger; import org.fluxoid.utils.UpdateCallback; import java.io.File; /** * @author www.cowboycoders.org * * A brake resistance controller which maps a speed to a brake * resistance. * * The mapping is implemented using a polynomial fit based on * observations of the head unit behavior. */ public class SpeedResistanceMapper extends AbstractController { private static final Mode SUPPORTED_MODE = Mode.TARGET_SLOPE; // Period at which the virtual speed will be updated (ms) private static final int POWER_MODEL_UPDATE_PERIOD_MS = 100; private static final int INITIAL_BRAKE_RESISTANCE = 20; // Coefficients from polynomial fit to brake resistance -> speed private static final double POLY_A = 8.728; private static final double POLY_B = 2.92e-1; private static final double POLY_C = 9.6e-1; private static final double POLY_D = -7.7e-2; private static final double POLY_E = 1.172e-1; private static final double POLY_F = 7.203; private static final double POLY_G = -2.55e-2; private static final double POLY_H = 1.82e-3; private static final double POLY_I = 4.917e-4; // Model to estimate bike speed from power private final PowerModel powerModel = new PowerModel(); // public SpeedResistanceMapper(BrakeModel bushidoModel) { // this.bushidoDataModel = bushidoModel; // bushidoDataModel.setResistance(INITIAL_BRAKE_RESISTANCE); // } /** * Calculates a brake resistance from the current virtual speed. * * The virtual speed is calculated using a power model taking into account * rider and bike weight (total weight), the current slope, etc. * * The polynomial fit has been generated from data based on the relationship * between the brake resistance and the virtual speed on the head unit. * * @return - estimated brake resistance */ public double getBrakeResistanceFromPolynomialFit() { final BrakeModel bushidoDataModel = getDataModel(); final double slope = bushidoDataModel.getSlope(); final double totalWeight = bushidoDataModel.getTotalWeight(); final double actualSpeed = bushidoDataModel.getActualSpeed(); // Use a polynomial fit to data ripped from the head unit to estimate // brake resistance from total weight, speed and slope final double brakeResistance = slope * POLY_A + totalWeight * POLY_B + totalWeight * slope * POLY_C + actualSpeed * POLY_D + Math.pow(actualSpeed, 2) * POLY_E + POLY_F + slope * actualSpeed * POLY_G + totalWeight * actualSpeed * POLY_H + slope * totalWeight * actualSpeed * POLY_I; return brakeResistance; } /** * Periodically updates the virtual speed which is required for estimating * the brake resistance * * This is called frequently for two reasons: * * 1. We want to ensure that the most recent resistance estimate is * always available to be sent to the brake. * 2. The power model requires * updating in sub-second intervals for the integration technique to work * with reasonable accuracy. */ private final UpdateCallback updateVirtualSpeed = new UpdateCallback() { @Override public void onUpdate(final Object newValue) { final BrakeModel bushidoDataModel = getDataModel(); powerModel.setGradientAsPercentage(bushidoDataModel.getSlope()); final double virtualSpeed = powerModel.updatePower((Double) newValue) * Conversions .METRES_PER_SECOND_TO_KM_PER_HOUR; bushidoDataModel.setVirtualSpeed(virtualSpeed); // Update the brake resistance from the current virtual speed bushidoDataModel .setAbsoluteResistance((int) getBrakeResistanceFromPolynomialFit()); logToCsv(VIRTUAL_SPEED_HEADING, virtualSpeed); logToCsv(ABSOLUTE_RESISTANCE_HEADING, bushidoDataModel.getAbsoluteResistance()); } }; private final FixedPeriodUpdater powerModelUpdater = new FixedPeriodUpdater( new Double(0), updateVirtualSpeed, POWER_MODEL_UPDATE_PERIOD_MS); @Override public double onPowerChange(final double power) { // Update the power with which the power model is updated with powerModelUpdater.update(new Double(power)); // Only starts once powerModelUpdater.start(); return power; } @Override public double onSpeedChange(final double speed) { logToCsv(ACTUAL_SPEED_HEADING, speed); final BrakeModel bushidoDataModel = getDataModel(); // return virtual speed instead! return bushidoDataModel.getVirtualSpeed(); } @Override public Mode getMode() { return SUPPORTED_MODE; } @Override public void onStart() { getDataModel().setResistance(INITIAL_BRAKE_RESISTANCE); } @Override public void onStop() { powerModelUpdater.stop(); } @Override protected SimpleCsvLogger getCsvLogger(File file) { SimpleCsvLogger logger = new SimpleCsvLogger(file, ACTUAL_SPEED_HEADING, VIRTUAL_SPEED_HEADING, ABSOLUTE_RESISTANCE_HEADING); logger.addTime(true); logger.append(true); return logger; } }