package org.cowboycoders.turbotrainers; import org.fluxoid.utils.Conversions; import org.fluxoid.utils.TrapezoidIntegrator; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeoutException; /** * Created by fluxoid on 17/03/15. */ public class DummyTrainer extends GenericTurboTrainer { private static final double MAX_CADENCE_CHANGE = 2; private static final double MAX_POWER_CHANGE = 5; private static final double HR_STEP = 2; // heart rate step size private TrapezoidIntegrator distanceIntegrator = new TrapezoidIntegrator(); private volatile double hr = 60; private volatile double power = 250; private volatile double cadence = 90; private volatile double speed = 0; private volatile double distance = 0; private volatile double slope = 0.0; public static final Mode[] SUPPORTED_MODES = new Mode[]{ Mode.TARGET_SLOPE }; { setSupportedModes(SUPPORTED_MODES); } /** * Crude HR model derived from curve fit * * @param power riders current power output * @param heartRate previous heart rate measurement */ private static int getModelHR(double power, double heartRate) { double targetHR; if (power <= 100) { targetHR = 60 + 0.3 * power; } else { targetHR = 56.07 * Math.log(power) - 162.43; } if (targetHR < 60) { targetHR = 60; } double diff = Math.abs(targetHR - heartRate); if (heartRate < targetHR) { heartRate += Math.min(diff, HR_STEP); } else if (heartRate > targetHR) { heartRate -= Math.min(diff, HR_STEP); } return (int) heartRate; } private PowerModel pm = new PowerModel(); private static double plusOrMinus(double curVal, double maxChange) { double result; if (Math.random() < 0.5) { result = curVal + Math.random() * maxChange; } else { result = curVal - Math.random() * maxChange; } // negative values make no sense for us if (result < 0.0) return 0.0; return result; } private final class PowerModelUpdater extends TimerTask { @Override public void run() { power = plusOrMinus(power, MAX_POWER_CHANGE); pm.updatePower(power); speed = pm.getVelocity() * Conversions.METRES_PER_SECOND_TO_KM_PER_HOUR; distanceIntegrator.add(getTimestamp(), pm.getVelocity()); distance = distanceIntegrator.getIntegral(); } } private final class DataSpoofer extends TimerTask { @Override public void run() { //change these at lower frequency hr = getModelHR(power, hr); cadence = plusOrMinus(cadence, MAX_CADENCE_CHANGE); for (TurboTrainerDataListener listener : getDataChangeListeners()) { listener.onCadenceChange(cadence); listener.onPowerChange(power); listener.onHeartRateChange(hr); listener.onSpeedChange(speed); listener.onDistanceChange(distance); } } } protected static double getTimestamp() { return System.nanoTime() / Math.pow(10, 9); } private Timer dataTimer = null; @Override public boolean supportsSpeed() { return true; } @Override public boolean supportsPower() { return true; } @Override public boolean supportsCadence() { return true; } @Override public boolean supportsHeartRate() { return true; } @Override public void setParameters(Parameters.CommonParametersInterface parameters) throws IllegalArgumentException { if (!(parameters instanceof Parameters.TargetSlope)) { throw new IllegalArgumentException("this class only supports target-slope"); } Parameters.TargetSlope tp = (Parameters.TargetSlope) parameters; slope = tp.getSlope(); pm.setAirDensity(parameters.getAirDensity()); pm.setCoefficentRollingResistance(parameters.getCoefficentRollingResistance()); pm.setCurrentBearing(parameters.getCurrentBearing()); pm.setDragArea(parameters.getDragArea()); pm.setTotalMass(parameters.getTotalWeight()); pm.setGradientAsPercentage(slope); pm.setMomentOfInertiaWheels(parameters.getMomentOfInertiaWheels()); pm.setOutsideRadiusTire(parameters.getOutsideRadiusTire()); pm.setWindDirectionDegrees(parameters.getWindDirectionDegrees()); pm.setWindSpeed(parameters.getWindSpeed()); pm.setIncrementalDragAreaSpokes(parameters.getIncrementalDragAreaSpokes()); } @Override public void start() throws TurboCommunicationException, InterruptedException, TimeoutException, IllegalStateException { dataTimer = new Timer(true); // update power model at high frequency dataTimer.scheduleAtFixedRate(new PowerModelUpdater(), 5000, 50); // spoof some data every second dataTimer.scheduleAtFixedRate(new DataSpoofer(), 5000, 1000); } @Override public void stop() throws InterruptedException, TimeoutException { if (dataTimer != null) { dataTimer.cancel(); dataTimer = null; } } }