/*
* Strongback
* Copyright 2015, Strongback and individual contributors by the @authors tag.
* See the COPYRIGHT.txt in the distribution for a full listing of individual
* contributors.
*
* Licensed under the MIT License; you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.strongback.hardware;
import java.util.function.DoubleSupplier;
import org.strongback.Strongback;
import org.strongback.annotation.Immutable;
import org.strongback.components.CurrentSensor;
import org.strongback.components.Gyroscope;
import org.strongback.components.Switch;
import org.strongback.components.TalonSRX;
import org.strongback.components.TemperatureSensor;
import org.strongback.components.VoltageSensor;
import edu.wpi.first.wpilibj.CANTalon;
import edu.wpi.first.wpilibj.CANTalon.TalonControlMode;
/**
* Talon speed controller with position and current sensor
*
* @author Nathan Brown
* @see TalonSRX
* @see CANTalon
*/
@Immutable
class HardwareTalonSRX implements TalonSRX {
protected static interface InputSensor extends Gyroscope {
public double rawPositionForAngleInDegrees( double angle );
public double angleInDegreesFromRawPosition( double position );
}
protected static final InputSensor NO_OP_SENSOR = new InputSensor() {
@Override
public double getAngle() {
return 0;
}
@Override
public double getRate() {
return 0;
}
@Override
public InputSensor zero() {
return this; // do nothing
}
@Override
public double rawPositionForAngleInDegrees(double angleInDegrees) {
return 0.0;
}
@Override
public double angleInDegreesFromRawPosition(double position) {
return 0.0;
}
};
protected static final class EncoderInputSensor implements InputSensor {
private double zero = 0.0;
private final DoubleSupplier positionInEdges;
private final DoubleSupplier velocityInEdgesPerCycle;
private final DoubleSupplier cyclePeriodInSeconds;
private final double pulsesPerDegree;
private final double edgesPerPulse;
protected EncoderInputSensor( DoubleSupplier positionInEdges, DoubleSupplier velocityInEdgesPerCycle,
double pulsesPerDegree, int edgesPerPulse, DoubleSupplier cyclesPeriodInSeconds ) {
this.positionInEdges = positionInEdges;
this.velocityInEdgesPerCycle = velocityInEdgesPerCycle;
this.cyclePeriodInSeconds = cyclesPeriodInSeconds;
this.pulsesPerDegree = pulsesPerDegree;
this.edgesPerPulse = edgesPerPulse;
}
@Override
public double rawPositionForAngleInDegrees(double angle) {
// Units: (degrees) x (pulses/degrees) * (edges/pulses) = (edges)
double relativeInput = angle * pulsesPerDegree * edgesPerPulse;
return relativeInput + zero;
}
@Override
public double angleInDegreesFromRawPosition(double position) {
// Units: (edges) x (pulse/edges) x (degrees/pulse) = degrees
return (position - zero) / edgesPerPulse / pulsesPerDegree;
}
@Override
public double getAngle() {
return angleInDegreesFromRawPosition(positionInEdges.getAsDouble());
}
@Override
public double getRate() {
// Units: (edges/cycle) * (pulses/edge) x (degrees/pulse) x (cycles/second) = (degrees/second)
return velocityInEdgesPerCycle.getAsDouble() / edgesPerPulse / pulsesPerDegree / cyclePeriodInSeconds.getAsDouble();
}
@Override
public Gyroscope zero() {
zero = positionInEdges.getAsDouble();
return this;
}
}
protected static final class AnalogInputSensor implements InputSensor {
private double zero = 0.0;
private final DoubleSupplier analogPosition;
private final DoubleSupplier changeInVoltsPerCycle;
private final DoubleSupplier cyclePeriodInSeconds;
private final double analogRange;
private final double analogTurnsPerVolt;
private final double voltageRange;
protected AnalogInputSensor( DoubleSupplier analogPosition, DoubleSupplier changeInVoltsPerCycle,
double analogRange, double analogTurnsPerVolt, double voltageRange, DoubleSupplier cyclePeriodInSeconds ) {
this.analogPosition = analogPosition;
this.changeInVoltsPerCycle = changeInVoltsPerCycle;
this.cyclePeriodInSeconds = cyclePeriodInSeconds;
this.analogRange = analogRange;
this.analogTurnsPerVolt = analogTurnsPerVolt;
this.voltageRange = voltageRange;
}
@Override
public double rawPositionForAngleInDegrees(double angle) {
// Units: (0-1023) / 1023 x (turns/volt) x (volts) x (degrees/turn) = degrees
// Units: (degrees) x (turns/degrees) x (1/volts) x (volts/turn) * 1023 = (0-1023)
double relativeInput = angle / 360.0 / voltageRange / analogTurnsPerVolt * analogRange;
return relativeInput + zero;
}
@Override
public double angleInDegreesFromRawPosition(double position) {
// Units: (0-1023) / 1023 x (turns/volt) x (volts) x (degrees/turn) = degrees
return (position - zero) / analogRange * analogTurnsPerVolt * voltageRange * 360.0;
}
@Override
public double getAngle() {
return angleInDegreesFromRawPosition(analogPosition.getAsDouble());
}
@Override
public double getRate() {
// Units: (0-1023)/cycle / 1023 x (turns/volt) x (volts) x (degrees/turn) x (cycles/second) = (degrees/second)
return changeInVoltsPerCycle.getAsDouble() / analogRange * analogTurnsPerVolt * voltageRange * 360.0
/ cyclePeriodInSeconds.getAsDouble();
}
@Override
public Gyroscope zero() {
zero = analogPosition.getAsDouble();
return this;
}
}
protected static EncoderInputSensor encoderInput(DoubleSupplier positionInPulses, DoubleSupplier velocityInPulsesPerCycle,
double pulsesPerDegree, int risesPerPulse, DoubleSupplier cyclePeriodInSeconds) {
if ( pulsesPerDegree <= 0.0000001d && pulsesPerDegree >= 0.0000001d ) return null;
return new EncoderInputSensor(positionInPulses, velocityInPulsesPerCycle, pulsesPerDegree, risesPerPulse, cyclePeriodInSeconds);
}
protected static AnalogInputSensor analogInput(DoubleSupplier analogPosition, DoubleSupplier changeInVoltsPerCycle,
double analogRange, double analogTurnsPerVolt, double voltageRange, DoubleSupplier cyclesPeriodInSeconds) {
if ( analogTurnsPerVolt <= 0.0000001d && analogTurnsPerVolt >= 0.0000001d ) return null;
return new AnalogInputSensor(analogPosition, changeInVoltsPerCycle, analogRange, analogTurnsPerVolt, voltageRange, cyclesPeriodInSeconds);
}
private static final double DEFAULT_ANALOG_RATE = 0.100;
private static final double DEFAULT_QUADRATURE_RATE = 0.100;
private static final double DEFAULT_FEEDBACK_RATE = 0.020;
private static final int RISES_PER_PULSE = 4; // 4x mode
private static final double MAX_ANALOG_VOLTAGE = 3.3; // 0-3.3V
private static final double MAX_ANALOG_RANGE = 1023; // 10 bits non-continuous
protected final CANTalon talon;
protected final InputSensor encoderInput;
protected final InputSensor analogInput;
protected final InputSensor selectedEncoderInput;
protected final InputSensor selectedAnalogInput;
protected volatile InputSensor selectedInput = NO_OP_SENSOR;
protected volatile double quadratureRateInSeconds = DEFAULT_QUADRATURE_RATE;
protected volatile double analogRateInSeconds = DEFAULT_ANALOG_RATE;
protected volatile double feedbackRateInSeconds = DEFAULT_FEEDBACK_RATE;
protected final Switch forwardLimitSwitch;
protected final Switch reverseLimitSwitch;
protected final CurrentSensor outputCurrent;
protected final VoltageSensor outputVoltage;
protected final VoltageSensor busVoltage;
protected final TemperatureSensor temperature;
protected final Faults instantaneousFaults;
protected final Faults stickyFaults;
HardwareTalonSRX(CANTalon talon, double pulsesPerDegree, double analogTurnsOverVoltageRange) {
this.talon = talon;
this.forwardLimitSwitch = talon::isRevLimitSwitchClosed;
this.reverseLimitSwitch = talon::isFwdLimitSwitchClosed;
this.outputCurrent = talon::getOutputCurrent;
this.outputVoltage = talon::getOutputVoltage;
this.busVoltage = talon::getBusVoltage;
this.temperature = talon::getTemperature;
this.encoderInput = encoderInput(talon::getEncPosition,
talon::getEncVelocity,
pulsesPerDegree,
RISES_PER_PULSE,
() -> quadratureRateInSeconds);
this.analogInput = analogInput(talon::getAnalogInPosition,
talon::getAnalogInVelocity,
MAX_ANALOG_RANGE,
analogTurnsOverVoltageRange / MAX_ANALOG_VOLTAGE,
MAX_ANALOG_VOLTAGE,
() -> analogRateInSeconds);
this.selectedEncoderInput = encoderInput(talon::getPosition,
talon::getSpeed,
pulsesPerDegree,
RISES_PER_PULSE,
() -> feedbackRateInSeconds);
this.selectedAnalogInput = analogInput(talon::getPosition,
talon::getSpeed,
MAX_ANALOG_RANGE,
analogTurnsOverVoltageRange / MAX_ANALOG_VOLTAGE,
MAX_ANALOG_VOLTAGE,
() -> feedbackRateInSeconds);
this.instantaneousFaults = new Faults() {
@Override
public Switch forwardLimitSwitch() {
return () -> talon.getFaultForLim() != 0;
}
@Override
public Switch reverseLimitSwitch() {
return () -> talon.getFaultRevLim() != 0;
}
@Override
public Switch forwardSoftLimit() {
return () -> talon.getFaultForSoftLim() != 0;
}
@Override
public Switch reverseSoftLimit() {
return () -> talon.getFaultRevSoftLim() != 0;
}
@Override
public Switch hardwareFailure() {
return () -> talon.getFaultHardwareFailure() != 0;
}
@Override
public Switch overTemperature() {
return () -> talon.getFaultOverTemp() != 0;
}
@Override
public Switch underVoltage() {
return () -> talon.getFaultUnderVoltage() != 0;
}
};
this.stickyFaults = new Faults() {
@Override
public Switch forwardLimitSwitch() {
return () -> talon.getStickyFaultForLim() != 0;
}
@Override
public Switch reverseLimitSwitch() {
return () -> talon.getStickyFaultRevLim() != 0;
}
@Override
public Switch forwardSoftLimit() {
return () -> talon.getStickyFaultForSoftLim() != 0;
}
@Override
public Switch reverseSoftLimit() {
return () -> talon.getStickyFaultRevSoftLim() != 0;
}
@Override
public Switch hardwareFailure() {
return () -> talon.getFaultHardwareFailure() != 0; // no sticky version!
}
@Override
public Switch overTemperature() {
return () -> talon.getStickyFaultOverTemp() != 0;
}
@Override
public Switch underVoltage() {
return () -> talon.getStickyFaultUnderVoltage() != 0;
}
};
}
@Override
public int getDeviceID() {
return talon.getDeviceID();
}
@Override
public double getSpeed() {
talon.changeControlMode(TalonControlMode.PercentVbus);
return talon.get();
}
@Override
public TalonSRX setSpeed(double speed) {
talon.changeControlMode(TalonControlMode.PercentVbus);
talon.set(speed);
return this;
}
@Override
public void stop() {
talon.enableBrakeMode(true);
talon.set(0);
}
@Override
public Gyroscope getEncoderInput() {
return encoderInput;
}
@Override
public Gyroscope getAnalogInput() {
return analogInput;
}
@Override
public Gyroscope getSelectedSensor() {
return selectedInput;
}
@Override
public TalonSRX setFeedbackDevice(FeedbackDevice device) {
talon.setFeedbackDevice(edu.wpi.first.wpilibj.CANTalon.FeedbackDevice.valueOf(device.value()));
switch(device) {
case ANALOG_POTENTIOMETER:
case ANALOG_ENCODER:
if ( selectedAnalogInput != null ) {
selectedInput = selectedAnalogInput;
} else {
Strongback.logger(getClass()).error("Unable to use the analog input for feedback, since the Talon SRX (device " + getDeviceID() + ") was not instantiated with an analog input. Check how this device was created using Strongback's Hardware class.");
selectedInput = NO_OP_SENSOR;
}
break;
case QUADRATURE_ENCODER:
case ENCODER_RISING:
if ( selectedEncoderInput != null ) {
selectedInput = selectedEncoderInput;
} else {
Strongback.logger(getClass()).error("Unable to use the quadrature encoder input for feedback, since the Talon SRX (device " + getDeviceID() + ") was not instantiated with an encoder input. Check how this device was created using Strongback's Hardware class.");
selectedInput = NO_OP_SENSOR;
}
break;
case ENCODER_FALLING:
// for 2015 the Talon SRX firmware did not support the falling or rising mode ...
selectedInput = NO_OP_SENSOR;
break;
}
return this;
}
@Override
public TalonSRX setStatusFrameRate(StatusFrameRate frameRate, int periodMillis) {
talon.setStatusFrameRateMs(edu.wpi.first.wpilibj.CANTalon.StatusFrameRate.valueOf(frameRate.value()), periodMillis);
double periodInSeconds = periodMillis / 1000.0;
switch(frameRate) {
case FEEDBACK:
feedbackRateInSeconds = periodInSeconds;
break;
case QUADRATURE_ENCODER:
quadratureRateInSeconds = periodInSeconds;
break;
case ANALOG_TEMPERATURE_BATTERY_VOLTAGE:
analogRateInSeconds = periodInSeconds;
break;
case GENERAL:
// nothing to set, since our code doesn't use the "general" frames
break;
}
return this;
}
@Override
public TalonSRX reverseSensor(boolean flip) {
talon.reverseSensor(flip);
return this;
}
@Override
public Switch getForwardLimitSwitch() {
return forwardLimitSwitch;
}
@Override
public Switch getReverseLimitSwitch() {
return reverseLimitSwitch;
}
@Override
public CurrentSensor getCurrentSensor() {
return outputCurrent;
}
@Override
public VoltageSensor getVoltageSensor() {
return outputVoltage;
}
@Override
public VoltageSensor getBusVoltageSensor() {
return busVoltage;
}
@Override
public TemperatureSensor getTemperatureSensor() {
return temperature;
}
@Override
public TalonSRX setForwardSoftLimit(int forwardLimitDegrees) {
// Compute the desired forward limit in terms of the current selected input sensor ...
if ( this.selectedInput != null ) {
double rawPosition = this.selectedInput.rawPositionForAngleInDegrees(forwardLimitDegrees);
talon.setForwardSoftLimit(rawPosition);
}
return this;
}
@Override
public HardwareTalonSRX enableForwardSoftLimit(boolean enable) {
talon.enableForwardSoftLimit(enable);
return this;
}
@Override
public HardwareTalonSRX setReverseSoftLimit(int reverseLimitDegrees) {
// Compute the desired reverse limit in terms of the current selected input sensor ...
if ( this.selectedInput != null ) {
double rawPosition = this.selectedInput.rawPositionForAngleInDegrees(reverseLimitDegrees);
talon.setReverseSoftLimit(rawPosition);
}
return this;
}
@Override
public HardwareTalonSRX enableReverseSoftLimit(boolean enable) {
talon.enableReverseSoftLimit(enable);
return this;
}
@Override
public TalonSRX enableLimitSwitch(boolean forward, boolean reverse) {
talon.enableLimitSwitch(forward, reverse);
return this;
}
@Override
public TalonSRX enableBrakeMode(boolean brake) {
talon.enableBrakeMode(brake);
return this;
}
@Override
public TalonSRX setForwardLimitSwitchNormallyOpen(boolean normallyOpen) {
talon.ConfigFwdLimitSwitchNormallyOpen(normallyOpen);
return this;
}
@Override
public TalonSRX setReverseLimitSwitchNormallyOpen(boolean normallyOpen) {
talon.ConfigRevLimitSwitchNormallyOpen(normallyOpen);
return this;
}
@Override
public TalonSRX setVoltageRampRate(double rampRate) {
talon.setVoltageRampRate(rampRate);
return this;
}
@Override
public Faults faults() {
return instantaneousFaults;
}
@Override
public Faults stickyFaults() {
return stickyFaults;
}
@Override
public TalonSRX clearStickyFaults() {
talon.clearStickyFaults();
return this;
}
@Override
public long getFirmwareVersion() {
return talon.GetFirmwareVersion();
}
@Override
public boolean isSafetyEnabled() {
return talon.isSafetyEnabled();
}
@Override
public TalonSRX setSafetyEnabled(boolean enabled) {
talon.setSafetyEnabled(enabled);
return this;
}
@Override
public double getExpiration() {
return talon.getExpiration();
}
@Override
public TalonSRX setExpiration(double timeout) {
talon.setExpiration(timeout);
return this;
}
@Override
public boolean isAlive() {
return talon.isAlive();
}
}