/* * Copyright 2013-2016 Cel Skeggs * Copyright 2015 Jake Springer * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. * * * This file contains code inspired by/based on code Copyright 2008-2014 FIRST. * To see the license terms of that code (modified BSD), see the root of the CCRE. */ package ccre.frc; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.NetworkInterface; import java.net.SocketException; import java.net.URL; import java.util.Enumeration; import java.util.Random; import java.util.jar.Manifest; import ccre.bus.I2CBus; import ccre.bus.RS232Bus; import ccre.bus.SPIBus; import ccre.channel.BooleanCell; import ccre.channel.BooleanInput; import ccre.channel.BooleanOutput; import ccre.channel.DerivedBooleanInput; import ccre.channel.DerivedFloatInput; import ccre.channel.EventCell; import ccre.channel.EventInput; import ccre.channel.FloatInput; import ccre.channel.FloatOutput; import ccre.cluck.Cluck; import ccre.concurrency.ReporterThread; import ccre.ctrl.CommunicationFailureExtendedMotor; import ccre.ctrl.ExtendedMotor; import ccre.ctrl.ExtendedMotorFailureException; import ccre.ctrl.Joystick; import ccre.ctrl.binding.ControlBindingCreator; import ccre.discrete.DerivedDiscreteInput; import ccre.discrete.DiscreteInput; import ccre.drivers.ByteFiddling; import ccre.drivers.ctre.talon.TalonExtendedMotor; import ccre.log.FileLogger; import ccre.log.Logger; import ccre.log.NetworkAutologger; import ccre.storage.Storage; import ccre.util.Version; import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary; import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tInstances; import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType; /** * The RoboRIO implementation of the FRCImplementation interface. Do not use * this! This should only be referenced from the build script. * * @see FRCImplementation * @author skeggsc */ public final class DirectFRCImplementation implements FRCImplementation { /** * The entry point for the Direct robot implementation. * * @param args the program arguments. ignored. */ public static void main(String[] args) { FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationReserve(); try { File version = new File("/tmp/frc_versions/FRC_Lib_Version.ini"); if (version.exists()) { version.delete(); } try (FileOutputStream output = new FileOutputStream(version)) { output.write(("CCRE " + Version.getShortVersion() + " by Team 1540").getBytes("UTF-8")); } } catch (IOException ex) { Logger.warning("Could not write version file", ex); } try { runMain(); Logger.severe("Robots don't quit!"); } catch (Throwable t) { t.printStackTrace(); Logger.severe("Uncaught exception!", t); } System.exit(1); } private static void runMain() { Logger.info("I am a CCRE-powered robot with version " + Version.getVersion() + "!"); DirectFRCImplementation robot = new DirectFRCImplementation(); FRCImplementationHolder.setImplementation(robot); // Cluck de-facto off-FMS port. Cluck.setupServer(1540); // SmartDashboard port, since it's unused with the CCRE Cluck.setupServer(1735); // First team-use port. Cluck.setupServer(5800); // Another team-use port. Cluck.setupServer(5805); try { robot.setupMain(); Cluck.getNode().notifyNetworkModified(); } catch (Throwable thr) { Logger.severe("Critical Code Failure in Robot Init", thr); return; } DirectDriverStation.init(); FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationObserveUserProgramStarting(); robot.mainloop(); } private void mainloop() { activeMode = null; while (true) { int word = FRCNetworkCommunicationsLibrary.NativeHALGetControlWord(); onFMS = (word & FRCNetworkCommunicationsLibrary.HAL_FMS_ATTACHED) != 0; Mode newmode = calcMode(word); if (newmode != activeMode) { activeMode = newmode; activeMode.start(this, onFMS); } if (DirectDriverStation.isNewControlData()) { switch (activeMode) { case DISABLED: FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationObserveUserProgramDisabled(); break; case AUTONOMOUS: FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationObserveUserProgramAutonomous(); break; case TELEOP: FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationObserveUserProgramTeleop(); break; case TEST: FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationObserveUserProgramTest(); break; } activeMode.periodic(this); } try { DirectDriverStation.waitForData(); } catch (InterruptedException e) { Logger.warning("Core thread interrupted... ignoring.", e); } } } private static final EventCell globalPeriodic = new EventCell(); /** * Initialized by usePCMCompressor if needed. */ private long pcmCompressor; private Mode activeMode = Mode.DISABLED; private boolean onFMS = false; private final EventCell[] startEvents, duringEvents; private final EventCell onInitComplete = new EventCell(); private final EventCell onChangeMode = new EventCell(); { int count = Mode.values().length; startEvents = new EventCell[count]; duringEvents = new EventCell[count]; for (int i = 0; i < count; i++) { startEvents[i] = new EventCell(); duringEvents[i] = new EventCell(); } } /** * Create and initialize a new DirectFRCImplementation. */ public DirectFRCImplementation() { FRCNetworkCommunicationsLibrary.FRCNetworkCommunicationUsageReportingReport((byte) tResourceType.kResourceType_Language, (byte) tInstances.kLanguage_Java, (byte) 0, "With the CCRE: the CommonChickenRuntimeEngine"); File rootDir = new File("/home/lvuser/ccre-storage"); if (!rootDir.exists() && !rootDir.mkdirs()) { Logger.warning("Could not create rootDir! Something might break..."); } Storage.setBaseDir(rootDir); NetworkAutologger.register(); FileLogger.register(); } private enum Mode { DISABLED("disabled", FRCMode.DISABLED), AUTONOMOUS("autonomous", FRCMode.AUTONOMOUS), TELEOP("teleop", FRCMode.TELEOP), TEST("test", FRCMode.TEST); public final FRCMode frcMode; public final String name; private Mode(String name, FRCMode mode) { this.name = name; this.frcMode = mode; } private EventCell getStart(DirectFRCImplementation impl) { return impl.startEvents[ordinal()]; } private EventCell getDuring(DirectFRCImplementation impl) { return impl.duringEvents[ordinal()]; } private void start(DirectFRCImplementation impl, boolean onFMS) { try { Logger.fine("Began " + name + (onFMS ? " on FMS" : " mode")); impl.onChangeMode.event(); getStart(impl).event(); } catch (Throwable thr) { Logger.severe("Critical Code Failure in " + name + " init", thr); } } private void periodic(DirectFRCImplementation impl) { getDuring(impl).safeEvent(); globalPeriodic.safeEvent(); } } private Mode calcMode(int word) { boolean enabled = (word & FRCNetworkCommunicationsLibrary.HAL_ENABLED) != 0; boolean autonomous = (word & FRCNetworkCommunicationsLibrary.HAL_AUTONOMOUS) != 0; boolean test = (word & FRCNetworkCommunicationsLibrary.HAL_TEST) != 0; boolean eStop = (word & FRCNetworkCommunicationsLibrary.HAL_ESTOP) != 0; boolean dsAttached = (word & FRCNetworkCommunicationsLibrary.HAL_DS_ATTACHED) != 0; // TODO: does including eStop here cause any issues? if (!enabled || !dsAttached || eStop) { return Mode.DISABLED; } else if (test) { return Mode.TEST; } else if (autonomous) { return Mode.AUTONOMOUS; } else { return Mode.TELEOP; } } private void setupMain() throws Throwable { Enumeration<URL> resources = DirectFRCImplementation.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); String name = null; while (resources != null && resources.hasMoreElements()) { Manifest manifest = new Manifest(resources.nextElement().openStream()); name = manifest.getMainAttributes().getValue("CCRE-Main"); } if (name == null) { throw new RuntimeException("Could not find MANIFEST-specified launchee!"); } Logger.info("Starting application: " + name); ((FRCApplication) Class.forName(name).newInstance()).setupRobot(); onInitComplete.event(); Logger.info("Hello, " + name + "!"); } @Override public BooleanOutput makeSolenoid(int module, int id) { final long port = DirectSolenoid.init(module, id); return value -> DirectSolenoid.set(port, value); } @Override public BooleanOutput makeDigitalOutput(int id) { DirectDigital.init(id, false); return value -> DirectDigital.set(id, value); } @Override public FloatInput makeAnalogInput(int id, EventInput updateOn) { long port = DirectAnalog.init(id); return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectAnalog.getAverageVoltage(port); } }; } @Override public FloatInput makeAnalogInput(int id, int averageBits, EventInput updateOn) { long port = DirectAnalog.init(id); DirectAnalog.configure(port, averageBits, 0);// TODO: oversample bits return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectAnalog.getAverageVoltage(port); } }; } @Override public BooleanInput makeDigitalInput(int id, EventInput updateOn) { DirectDigital.init(id, true); return new DerivedBooleanInput(updateOn) { @Override protected boolean apply() { return DirectDigital.get(id); } }; } @Override public BooleanInput makeDigitalInputByInterrupt(int id) { DirectDigital.init(id, true); DirectDigital.initInterruptsSynchronous(id, true, true); BooleanCell out = new BooleanCell(DirectDigital.get(id)); new ReporterThread("Interrupt-Handler") { @Override protected void threadBody() { while (true) { // TODO: use this return value for optimization boolean n = DirectDigital.waitForInterrupt(id, 10.0f, false); out.safeSet(DirectDigital.get(id)); } } }.start(); return out; } @Override public FloatOutput makeServo(int id, final float minInput, float maxInput) { if (minInput == maxInput) { throw new IllegalArgumentException("Servos cannot have their extrema be the same!"); } DirectPWM.init(id, DirectPWM.TYPE_SERVO); return f -> DirectPWM.set(id, (f - minInput) / (maxInput - minInput)); } @Override public BooleanInput getIsDisabled() { return new DerivedBooleanInput(onChangeMode) { @Override protected boolean apply() { return activeMode == Mode.DISABLED; } }; } @Override public BooleanInput getIsAutonomous() { return new DerivedBooleanInput(onChangeMode) { @Override protected boolean apply() { return activeMode == Mode.AUTONOMOUS; } }; } @Override public BooleanInput getIsTest() { return new DerivedBooleanInput(onChangeMode) { @Override protected boolean apply() { return activeMode == Mode.TEST; } }; } @Override public BooleanInput getIsFMS() { return new DerivedBooleanInput(onChangeMode) { @Override protected boolean apply() { return onFMS; } }; } @Override public FloatInput makeEncoder(int channelA, int channelB, boolean reverse, EventInput resetWhen, EventInput updateOn) { long encoder = DirectEncoder.init(channelA, channelB, reverse); if (resetWhen != null) { resetWhen.send(() -> DirectEncoder.reset(encoder)); } return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectEncoder.get(encoder); } }; } @Override public FloatInput makeCounter(int channelUp, int channelDown, EventInput resetWhen, EventInput updateOn, int mode) { // unused happens to be -1, but this makes more sense than comparing // with -1 if (channelUp == FRC.UNUSED && channelDown == FRC.UNUSED) { Logger.warning("Neither channelUp nor channelDown was provided to makeCounter."); return FloatInput.zero; } if (channelUp != FRC.UNUSED && (channelUp < 0 || channelUp >= DirectDigital.DIGITAL_PINS)) { throw new RuntimeException("Invalid up channel: " + channelUp); } if (channelDown != FRC.UNUSED && (channelDown < 0 || channelDown >= DirectDigital.DIGITAL_PINS)) { throw new RuntimeException("Invalid down channel: " + channelDown); } DirectDigital.init(channelUp, true); DirectDigital.init(channelDown, true); long counter = DirectCounter.init(channelUp, channelDown, mode); if (resetWhen != null) { resetWhen.send(() -> { DirectCounter.clearDownSource(counter); DirectCounter.clearUpSource(counter); }); } return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectCounter.get(counter); } }; } @Override public BooleanOutput makeRelayForwardOutput(int channel) { long relay = DirectRelay.init(channel); return (bln) -> DirectRelay.setForward(relay, bln); } @Override public BooleanOutput makeRelayReverseOutput(int channel) { long relay = DirectRelay.init(channel); return (bln) -> DirectRelay.setReverse(relay, bln); } @Override public FloatInput makeGyro(int port, double sensitivity, EventInput evt, EventInput updateOn) { long gyro; try { gyro = DirectGyro.init(port); } catch (InterruptedException e) { throw new RuntimeException("interrupted during Gyro calibration", e); } if (evt != null) { evt.send(() -> DirectGyro.reset(gyro)); } return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectGyro.getAngle(gyro, port, sensitivity); } }; } @Override public FloatOutput makeMotor(int id, int type) { switch (type) { case JAGUAR: DirectPWM.init(id, DirectPWM.TYPE_JAGUAR); break; case VICTOR: DirectPWM.init(id, DirectPWM.TYPE_VICTOR); break; case TALON: DirectPWM.init(id, DirectPWM.TYPE_TALON); break; case VICTORSP: DirectPWM.init(id, DirectPWM.TYPE_VICTORSP); break; case SPARK: DirectPWM.init(id, DirectPWM.TYPE_SPARK); break; case SD540: DirectPWM.init(id, DirectPWM.TYPE_SD540); break; case TALONSRX: DirectPWM.init(id, DirectPWM.TYPE_TALONSRX); break; default: throw new IllegalArgumentException("Unknown motor type: " + type); } return (f) -> DirectPWM.set(id, f); } @Override public EventInput getGlobalPeriodic() { return globalPeriodic; } @Override public EventInput getStartAuto() { return Mode.AUTONOMOUS.getStart(this); } @Override public EventInput getDuringAuto() { return Mode.AUTONOMOUS.getDuring(this); } @Override public EventInput getStartTele() { return Mode.TELEOP.getStart(this); } @Override public EventInput getDuringTele() { return Mode.TELEOP.getDuring(this); } @Override public EventInput getStartTest() { return Mode.TEST.getStart(this); } @Override public EventInput getDuringTest() { return Mode.TEST.getDuring(this); } @Override public EventInput getStartDisabled() { return Mode.DISABLED.getStart(this); } @Override public EventInput getDuringDisabled() { return Mode.DISABLED.getDuring(this); } private synchronized long getPCMCompressor() { if (pcmCompressor == 0) { // TODO: Provide all PCM ids pcmCompressor = DirectCompressor.init(0); } return pcmCompressor; } @Override public BooleanOutput usePCMCompressor() { DirectCompressor.setClosedLoop(getPCMCompressor(), true); return (on) -> DirectCompressor.setClosedLoop(getPCMCompressor(), on); } @Override public BooleanInput getPCMPressureSwitch(EventInput updateOn) { getPCMCompressor(); return new DerivedBooleanInput(updateOn) { @Override protected boolean apply() { return DirectCompressor.getPressureSwitch(getPCMCompressor()); } }; } @Override public BooleanInput getPCMCompressorRunning(EventInput updateOn) { getPCMCompressor(); return new DerivedBooleanInput(updateOn) { @Override protected boolean apply() { return DirectCompressor.getCompressorRunning(getPCMCompressor()); } }; } @Override public FloatInput getPCMCompressorCurrent(EventInput updateOn) { getPCMCompressor(); return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectCompressor.getCompressorCurrent(getPCMCompressor()); } }; } // TODO: Add the rest of the PCM and PDP accessors. @Override public FloatInput getPDPTotalCurrent(EventInput updateOn) { return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectPDP.getTotalCurrent(0); } }; } @Override public FloatInput getPDPChannelCurrent(final int channel, EventInput updateOn) { DirectPDP.checkChannel(channel); return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectPDP.getCurrent(channel, 0); } }; } @Override public FloatInput getPDPVoltage(EventInput updateOn) { return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectPDP.getVoltage(0); } }; } @Override public RS232Bus makeRS232_Onboard(String deviceName) { return (baudRate, parity, stopBits, timeout, dataBits) -> new RS232Direct(DirectRS232.PORT_ONBOARD, baudRate, parity, stopBits, timeout, dataBits); } @Override public RS232Bus makeRS232_MXP(String deviceName) { return (baudRate, parity, stopBits, timeout, dataBits) -> new RS232Direct(DirectRS232.PORT_MXP, baudRate, parity, stopBits, timeout, dataBits); } @Override public RS232Bus makeRS232_USB(String deviceName) { return (baudRate, parity, stopBits, timeout, dataBits) -> new RS232Direct(DirectRS232.PORT_USB, baudRate, parity, stopBits, timeout, dataBits); } @Override public I2CBus makeI2C_Onboard(String deviceName) { return (deviceAddress) -> new I2CPortDirect(DirectI2C.PORT_ONBOARD, deviceAddress); } @Override public I2CBus makeI2C_MXP(String deviceName) { return (deviceAddress) -> new I2CPortDirect(DirectI2C.PORT_MXP, deviceAddress); } @Override public SPIBus makeSPI_Onboard(int cs, String deviceName) { return (hertz, isMSB, dataOnFalling, clockActiveLow, chipSelectActiveLow) -> new SPIPortDirect(DirectSPI.portForCS(cs), hertz, isMSB, dataOnFalling, clockActiveLow, chipSelectActiveLow); } @Override public SPIBus makeSPI_MXP(String deviceName) { return (hertz, isMSB, dataOnFalling, clockActiveLow, chipSelectActiveLow) -> new SPIPortDirect(DirectSPI.PORT_MXP, hertz, isMSB, dataOnFalling, clockActiveLow, chipSelectActiveLow); } @Override public Joystick getJoystick(int id) { if (id < 1 || id > 6) { throw new IllegalArgumentException("Joystick " + id + " is not a valid joystick number."); } return new CJoystickDirect(id, globalPeriodic); } @Override public FloatInput getBatteryVoltage(EventInput updateOn) { DirectPower.init(); return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectPower.getBatteryVoltage(); } }; } @Override public ExtendedMotor makeCANJaguar(int deviceNumber) { try { return new ExtendedJaguarDirect(deviceNumber); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted during CAN Jaguar initialization"); } catch (ExtendedMotorFailureException ex) { Logger.severe("Could not connect to CAN Jaguar " + deviceNumber, ex); return new CommunicationFailureExtendedMotor("Could not connect to CAN Jaguar " + deviceNumber); } } @Override public TalonExtendedMotor makeCANTalon(int deviceNumber) { return new ExtendedTalonDirect(deviceNumber); } @Override public FloatInput getChannelVoltage(int powerChannel, EventInput updateOn) { if (DirectPower.readChannelVoltage(powerChannel) == -1) { Logger.warning("Unknown power channel: " + powerChannel); } return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectPower.readChannelVoltage(powerChannel); } }; } @Override public FloatInput getChannelCurrent(int powerChannel, EventInput updateOn) { if (DirectPower.readChannelCurrent(powerChannel) == -1) { Logger.warning("Unknown power channel: " + powerChannel); } return new DerivedFloatInput(updateOn) { @Override protected float apply() { return DirectPower.readChannelCurrent(powerChannel); } }; } @Override public BooleanInput getChannelEnabled(int powerChannel, EventInput updateOn) { return new DerivedBooleanInput(updateOn) { @Override protected boolean apply() { return DirectPower.readChannelEnabled(powerChannel); } }; } @Override public ControlBindingCreator tryMakeControlBindingCreator(String title) { return null; } @Override public EventInput getOnInitComplete() { return onInitComplete; } @Override public DiscreteInput<FRCMode> getMode() { return new DerivedDiscreteInput<FRCMode>(FRCMode.discreteType, onChangeMode) { @Override protected FRCMode apply() { return activeMode.frcMode; } }; } @Override public String getUniqueIdentifier() { try { byte[] mac = NetworkInterface.getByName("eth0").getHardwareAddress(); StringBuilder sb = new StringBuilder(ByteFiddling.toHex(mac[0])); for (int i = 1; i < mac.length; i++) { sb.append(':').append(ByteFiddling.toHex(mac[i])); } return sb.toString(); } catch (SocketException e) { Logger.warning("Could not find unique identifier; falling back to random ID", e); return "unknown-" + Integer.toHexString(new Random().nextInt()); } } }