diff --git a/strongback-testing/src/org/strongback/mock/Mock.java b/strongback-testing/src/org/strongback/mock/Mock.java index 130e9d4..b582e1e 100644 --- a/strongback-testing/src/org/strongback/mock/Mock.java +++ b/strongback-testing/src/org/strongback/mock/Mock.java @@ -25,6 +25,25 @@ */ public class Mock { + /** + * Create a mock power panel. + * + * @return the mock power panel; never null + */ + public static MockPowerPanel powerPanel() { + return new MockPowerPanel(16); + } + + /** + * Create a mock pneumatics module. This method can be called more than once to represent a robot with multiple pneumatics + * modules. + * + * @return the mock pneumatics module; never null + */ + public static MockPneumaticsModule pnuematicsModule() { + return new MockPneumaticsModule(); + } + /** * Create a mock clock. * @@ -175,6 +194,7 @@ public static MockSolenoid manualSolenoid() { /** * Create a relay that operates instantaneously. + * * @return the mock relay; never null */ public static MockRelay relay() { diff --git a/strongback-testing/src/org/strongback/mock/MockAccelerometer.java b/strongback-testing/src/org/strongback/mock/MockAccelerometer.java index 5f1df66..3552547 100644 --- a/strongback-testing/src/org/strongback/mock/MockAccelerometer.java +++ b/strongback-testing/src/org/strongback/mock/MockAccelerometer.java @@ -46,4 +46,8 @@ public MockAccelerometer setAcceleration(double accel) { return this; } + @Override + public String toString() { + return Double.toString(getAcceleration()) + " g/s\u00B2"; + } } \ No newline at end of file diff --git a/strongback-testing/src/org/strongback/mock/MockAngleSensor.java b/strongback-testing/src/org/strongback/mock/MockAngleSensor.java index ab85ba1..66c336e 100644 --- a/strongback-testing/src/org/strongback/mock/MockAngleSensor.java +++ b/strongback-testing/src/org/strongback/mock/MockAngleSensor.java @@ -50,4 +50,8 @@ public MockAngleSensor setAngle(double angle) { return this; } + @Override + public String toString() { + return Double.toString(getAngle()) + "\u00B0"; + } } diff --git a/strongback-testing/src/org/strongback/mock/MockClock.java b/strongback-testing/src/org/strongback/mock/MockClock.java index 52ad55b..b41b30c 100644 --- a/strongback-testing/src/org/strongback/mock/MockClock.java +++ b/strongback-testing/src/org/strongback/mock/MockClock.java @@ -69,4 +69,8 @@ public long currentTimeInMicros() { return ticker.get(); } + @Override + public String toString() { + return Long.toString(currentTimeInMillis()) + " ms"; + } } diff --git a/strongback-testing/src/org/strongback/mock/MockCompass.java b/strongback-testing/src/org/strongback/mock/MockCompass.java index 821dc71..82cc155 100644 --- a/strongback-testing/src/org/strongback/mock/MockCompass.java +++ b/strongback-testing/src/org/strongback/mock/MockCompass.java @@ -49,4 +49,9 @@ public MockCompass setAngle(double angle) { super.setValue(angle); return this; } + + @Override + public String toString() { + return Double.toString(getHeading()) + "\u00B0 heading (" + getAngle() + "\u00B0)"; + } } diff --git a/strongback-testing/src/org/strongback/mock/MockCurrentSensor.java b/strongback-testing/src/org/strongback/mock/MockCurrentSensor.java index 93b3967..12da1e8 100644 --- a/strongback-testing/src/org/strongback/mock/MockCurrentSensor.java +++ b/strongback-testing/src/org/strongback/mock/MockCurrentSensor.java @@ -46,4 +46,9 @@ public MockCurrentSensor setCurrent(double currentInAmps) { return this; } + @Override + public String toString() { + return Double.toString(getCurrent()) + "A"; + } + } diff --git a/strongback-testing/src/org/strongback/mock/MockDistanceSensor.java b/strongback-testing/src/org/strongback/mock/MockDistanceSensor.java index 4a7b038..b3b568b 100644 --- a/strongback-testing/src/org/strongback/mock/MockDistanceSensor.java +++ b/strongback-testing/src/org/strongback/mock/MockDistanceSensor.java @@ -62,4 +62,8 @@ public MockDistanceSensor setDistanceInFeet(double distance) { return setDistanceInInches(distance * 12.0); } + @Override + public String toString() { + return Double.toString(getDistanceInInches()) + " inches"; + } } diff --git a/strongback-testing/src/org/strongback/mock/MockMotor.java b/strongback-testing/src/org/strongback/mock/MockMotor.java index 5679a8c..c08a534 100644 --- a/strongback-testing/src/org/strongback/mock/MockMotor.java +++ b/strongback-testing/src/org/strongback/mock/MockMotor.java @@ -43,6 +43,7 @@ public MockMotor setSpeed(double speed) { return this; } + @Override public MockMotor invert() { return new MockMotor(speed) { @Override @@ -58,4 +59,9 @@ public double getSpeed() { }; } + + @Override + public String toString() { + return Double.toString(getSpeed()); + } } diff --git a/strongback-testing/src/org/strongback/mock/MockPneumaticsModule.java b/strongback-testing/src/org/strongback/mock/MockPneumaticsModule.java new file mode 100644 index 0000000..5455b82 --- /dev/null +++ b/strongback-testing/src/org/strongback/mock/MockPneumaticsModule.java @@ -0,0 +1,175 @@ +/* + * 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.mock; + +import org.strongback.components.Fuse; +import org.strongback.components.PneumaticsModule; +import org.strongback.components.Switch; + +/** + * A mock implementation of the {@link PneumaticsModule} that allows the caller to control the {@link #lowPressureSwitch()}. + * When the module's {@link #automaticMode()} is enabled, then triggering the {@link #lowPressureSwitch() low pressure switch} + * will start the compressor, and {@link MockSwitch#setNotTriggered() un-triggering} the {@link #lowPressureSwitch() low + * pressure switch} will stop the compressor. + * + * @author Randall Hauch + */ +public class MockPneumaticsModule implements PneumaticsModule { + + public static double COMPRESSOR_CURRENT_WHEN_RUNNING = 10.0; + + private final StickyFaults stickyFaults = new StickyFaults(); + private final MockFaults instantaneousFaults = new MockFaults(stickyFaults); + private final MockCurrentSensor current = new MockCurrentSensor(); + private final MockSwitch running = new MockSwitch().setNotTriggered(); + private final MockSwitch lowPressure = new MockSwitch() { + @Override + public MockSwitch setTriggered(boolean triggered) { + super.setTriggered(triggered); + if (automatic.isOn()) { + // The caller is manually triggering the switch, but this affects whether compressor runs ... + runCompressor(triggered); + } + return this; + } + }; + private final MockRelay automatic = new MockRelay() { + @Override + public MockRelay off() { + super.off(); + runCompressor(false); + return this; + } + + @Override + public MockRelay on() { + super.on(); + return this; + } + }; + + public MockPneumaticsModule() { + automatic.on(); + lowPressure.setNotTriggered(); + } + + private void runCompressor( boolean isRunning ) { + if ( isRunning ) { + running.setTriggered(isRunning); + current.setCurrent(COMPRESSOR_CURRENT_WHEN_RUNNING); + } else { + running.setNotTriggered(); + current.setCurrent(0.0); + } + } + + @Override + public MockCurrentSensor compressorCurrent() { + return current; + } + + @Override + public Switch compressorRunningSwitch() { + return running; + } + + @Override + public MockSwitch lowPressureSwitch() { + return lowPressure; + } + + @Override + public MockRelay automaticMode() { + return automatic; + } + + /** + * These faults clear immediately after they are {@link Fuse#trigger() triggered}. + * @see #compressorStickyFaults() + */ + @Override + public MockFaults compressorFaults() { + return instantaneousFaults; + } + + @Override + public Faults compressorStickyFaults() { + return stickyFaults; + } + + @Override + public MockPneumaticsModule clearStickyFaults() { + stickyFaults.reset(); + return this; + } + + private void triggerFault( MockSwitch stickySwitch ) { + // Any fault should always stop the compressor ... + stickySwitch.setTriggered(); + runCompressor(false); + } + + public class MockFaults implements Faults { + private final Fuse currentTooHigh; + private final Fuse notConnected; + private final Fuse shorted; + protected MockFaults( StickyFaults sticky ) { + // These should trip the sticky faults and then immediately reset ... + currentTooHigh = Fuse.instantaneous(()->triggerFault(sticky.currentTooHigh)); + notConnected = Fuse.instantaneous(()->triggerFault(sticky.notConnected)); + shorted = Fuse.instantaneous(()->triggerFault(sticky.shorted)); + } + + @Override + public Fuse currentTooHigh() { + return currentTooHigh; + } + @Override + public Fuse notConnected() { + return notConnected; + } + @Override + public Fuse shorted() { + return shorted; + } + } + + protected static class StickyFaults implements Faults { + private final MockSwitch currentTooHigh = new MockSwitch().setNotTriggered(); + private final MockSwitch notConnected = new MockSwitch().setNotTriggered(); + private final MockSwitch shorted = new MockSwitch().setNotTriggered(); + + @Override + public Switch currentTooHigh() { + return currentTooHigh; + } + @Override + public Switch notConnected() { + return notConnected; + } + @Override + public Switch shorted() { + return shorted; + } + protected void reset() { + currentTooHigh.setNotTriggered(); + notConnected.setNotTriggered(); + shorted.setNotTriggered(); + } + } + +} diff --git a/strongback-testing/src/org/strongback/mock/MockPowerPanel.java b/strongback-testing/src/org/strongback/mock/MockPowerPanel.java new file mode 100644 index 0000000..436e84b --- /dev/null +++ b/strongback-testing/src/org/strongback/mock/MockPowerPanel.java @@ -0,0 +1,66 @@ +/* + * 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.mock; + +import java.util.ArrayList; +import java.util.List; + +import org.strongback.components.PowerPanel; + +/** + * A mock implementation of the {@link PowerPanel}, which has 16 channels + * @author Randall Hauch + */ +public class MockPowerPanel implements PowerPanel { + + private final List channels; + private final MockTemperatureSensor temperature = new MockTemperatureSensor().setTemperature(72.0); + private final MockCurrentSensor totalCurrent = new MockCurrentSensor().setCurrent(10.0); + private final MockVoltageSensor voltage = new MockVoltageSensor().setVoltage(12.0); + + /** + * Create a mock power panel with the specified number of channels. + * @param numChannels the number of channels; must be positive + */ + public MockPowerPanel( int numChannels ) { + channels = new ArrayList<>(numChannels); + for ( int i= 0; i!= numChannels; ++i ) { + channels.add(new MockCurrentSensor()); + } + } + + @Override + public MockCurrentSensor getCurrentSensor(int channel) { + return channels.get(channel); + } + + @Override + public MockTemperatureSensor getTemperatureSensor() { + return temperature; + } + + @Override + public MockCurrentSensor getTotalCurrentSensor() { + return totalCurrent; + } + + @Override + public MockVoltageSensor getVoltageSensor() { + return voltage; + } + +} diff --git a/strongback-testing/src/org/strongback/mock/MockRelay.java b/strongback-testing/src/org/strongback/mock/MockRelay.java index 464bd1b..37b04f4 100644 --- a/strongback-testing/src/org/strongback/mock/MockRelay.java +++ b/strongback-testing/src/org/strongback/mock/MockRelay.java @@ -65,4 +65,9 @@ public MockRelay switchingOn() { public State state() { return state; } + + @Override + public String toString() { + return state().name(); + } } diff --git a/strongback-testing/src/org/strongback/mock/MockSolenoid.java b/strongback-testing/src/org/strongback/mock/MockSolenoid.java index 783510e..884c97e 100644 --- a/strongback-testing/src/org/strongback/mock/MockSolenoid.java +++ b/strongback-testing/src/org/strongback/mock/MockSolenoid.java @@ -63,4 +63,9 @@ public MockSolenoid stop() { direction = Direction.STOPPED; return this; } + + @Override + public String toString() { + return getDirection().name(); + } } diff --git a/strongback-testing/src/org/strongback/mock/MockTemperatureSensor.java b/strongback-testing/src/org/strongback/mock/MockTemperatureSensor.java index ee94399..e4d58ea 100644 --- a/strongback-testing/src/org/strongback/mock/MockTemperatureSensor.java +++ b/strongback-testing/src/org/strongback/mock/MockTemperatureSensor.java @@ -46,4 +46,8 @@ public MockTemperatureSensor setTemperature(double temp) { return this; } + @Override + public String toString() { + return Double.toString(getTemperatureInFahrenheit()) + "\u00B0F"; + } } diff --git a/strongback-testing/src/org/strongback/mock/MockThreeAxisAccelerometer.java b/strongback-testing/src/org/strongback/mock/MockThreeAxisAccelerometer.java index 6bdb83e..8b6bb7b 100644 --- a/strongback-testing/src/org/strongback/mock/MockThreeAxisAccelerometer.java +++ b/strongback-testing/src/org/strongback/mock/MockThreeAxisAccelerometer.java @@ -36,4 +36,9 @@ public class MockThreeAxisAccelerometer extends MockTwoAxisAccelerometer impleme public MockAccelerometer getZDirection() { return z; } + + @Override + public String toString() { + return "" + getXDirection().getAcceleration() + ", " + getYDirection().getAcceleration() + ", " + getZDirection().getAcceleration() + " g/s\u00B2"; + } } diff --git a/strongback-testing/src/org/strongback/mock/MockTwoAxisAccelerometer.java b/strongback-testing/src/org/strongback/mock/MockTwoAxisAccelerometer.java index 16a8f93..d8bc675 100644 --- a/strongback-testing/src/org/strongback/mock/MockTwoAxisAccelerometer.java +++ b/strongback-testing/src/org/strongback/mock/MockTwoAxisAccelerometer.java @@ -41,4 +41,9 @@ public MockAccelerometer getXDirection() { public MockAccelerometer getYDirection() { return y; } + + @Override + public String toString() { + return "" + getXDirection().getAcceleration() + ", " + getYDirection().getAcceleration() + " g/s\u00B2"; + } } diff --git a/strongback-testing/src/org/strongback/mock/MockVoltageSensor.java b/strongback-testing/src/org/strongback/mock/MockVoltageSensor.java index 2071a97..133695f 100644 --- a/strongback-testing/src/org/strongback/mock/MockVoltageSensor.java +++ b/strongback-testing/src/org/strongback/mock/MockVoltageSensor.java @@ -46,4 +46,8 @@ public MockVoltageSensor setVoltage(double voltage) { return this; } + @Override + public String toString() { + return Double.toString(getVoltage()) + " V"; + } } diff --git a/strongback-tests/src/org/strongback/mock/MockPneumaticsModuleTest.java b/strongback-tests/src/org/strongback/mock/MockPneumaticsModuleTest.java new file mode 100644 index 0000000..75fd07b --- /dev/null +++ b/strongback-tests/src/org/strongback/mock/MockPneumaticsModuleTest.java @@ -0,0 +1,143 @@ +/* + * 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.mock; + +import static org.fest.assertions.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; + +public class MockPneumaticsModuleTest { + + private MockPneumaticsModule module; + + @Before + public void beforeEach() { + this.module = new MockPneumaticsModule(); + } + + @Test + public void shouldNotBeAutomaticModeByDefault() { + assertThat(module.automaticMode().isOn()).isTrue(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + } + + @Test + public void shouldNotRunCompressorAutomaticallyWhenSetToAutomaticModeAndLowPressureIsNotTriggered() { + module.lowPressureSwitch().setNotTriggered(); // start out with enough pressure + module.automaticMode().on(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + + // drop pressure should run compressor ... + module.lowPressureSwitch().setTriggered(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(true); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(MockPneumaticsModule.COMPRESSOR_CURRENT_WHEN_RUNNING); + + // raise pressure should turn off compressor ... + module.lowPressureSwitch().setNotTriggered(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + + // drop pressure should run compressor ... + module.lowPressureSwitch().setTriggered(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(true); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(MockPneumaticsModule.COMPRESSOR_CURRENT_WHEN_RUNNING); + + // Turn off auto mode when running ... + module.automaticMode().off(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + } + + @Test + public void shouldRunCompressorAutomaticallyWhenSetToAutomaticModeAndLowPressureIsTriggered() { + module.lowPressureSwitch().setTriggered(); // start out with low pressure + module.automaticMode().on(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(true); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(MockPneumaticsModule.COMPRESSOR_CURRENT_WHEN_RUNNING); + + // raise pressure should turn off compressor ... + module.lowPressureSwitch().setNotTriggered(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + + // drop pressure should run compressor ... + module.lowPressureSwitch().setTriggered(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(true); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(MockPneumaticsModule.COMPRESSOR_CURRENT_WHEN_RUNNING); + + // raise pressure should turn off compressor ... + module.lowPressureSwitch().setNotTriggered(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + + // Turn off auto mode when not running ... + module.automaticMode().off(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + } + + @Test + public void shouldKeepStickyFaultStates() { + assertThat(module.compressorFaults().currentTooHigh().isTriggered()).isEqualTo(false); + assertThat(module.compressorFaults().notConnected().isTriggered()).isEqualTo(false); + assertThat(module.compressorFaults().shorted().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().currentTooHigh().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().notConnected().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().shorted().isTriggered()).isEqualTo(false); + + module.compressorFaults().currentTooHigh().trigger(); + assertThat(module.compressorFaults().currentTooHigh().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().currentTooHigh().isTriggered()).isEqualTo(true); + + // Start the compressor ... + module.lowPressureSwitch().setTriggered(); // start out with low pressure + module.automaticMode().on(); + + module.compressorFaults().notConnected().trigger(); + assertThat(module.compressorFaults().notConnected().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().notConnected().isTriggered()).isEqualTo(true); + + // Compressor should stop after a fault + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + + // But we can start it again ... + module.lowPressureSwitch().setTriggered(); + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(true); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(MockPneumaticsModule.COMPRESSOR_CURRENT_WHEN_RUNNING); + + module.compressorFaults().shorted().trigger(); + assertThat(module.compressorFaults().shorted().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().shorted().isTriggered()).isEqualTo(true); + + // Compressor should stop after a fault + assertThat(module.compressorRunningSwitch().isTriggered()).isEqualTo(false); + assertThat(module.compressorCurrent().getCurrent()).isEqualTo(0.0); + + module.clearStickyFaults(); + + assertThat(module.compressorFaults().currentTooHigh().isTriggered()).isEqualTo(false); + assertThat(module.compressorFaults().notConnected().isTriggered()).isEqualTo(false); + assertThat(module.compressorFaults().shorted().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().currentTooHigh().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().notConnected().isTriggered()).isEqualTo(false); + assertThat(module.compressorStickyFaults().shorted().isTriggered()).isEqualTo(false); + } + +} diff --git a/strongback/src/org/strongback/components/Fuse.java b/strongback/src/org/strongback/components/Fuse.java index 794d111..03ffeb9 100644 --- a/strongback/src/org/strongback/components/Fuse.java +++ b/strongback/src/org/strongback/components/Fuse.java @@ -18,6 +18,7 @@ import java.util.concurrent.TimeUnit; +import org.strongback.Strongback; import org.strongback.annotation.ThreadSafe; /** @@ -26,8 +27,8 @@ @ThreadSafe public interface Fuse extends Switch { /** - * Trigger the fuse. Once this method is called, the {@link #isTriggered()} switches but then will never change - * until it is {@link #reset()}. + * Trigger the fuse. Once this method is called, the {@link #isTriggered()} switches but then will never change until it is + * {@link #reset()}. * * @return this object to allow for chaining methods together; never null */ @@ -73,6 +74,44 @@ public String toString() { }; } + /** + * Create a simple fuse that is manually {@link #trigger() triggered} and manually {@link #reset()}. + * + * @param whenTriggered the function that is to be called when the fuse is triggered; may be null + * @return the fuse; never null + */ + public static Fuse instantaneous( Runnable whenTriggered ) { + return new Fuse() { + + @Override + public boolean isTriggered() { + return false; + } + + @Override + public Fuse trigger() { + if (whenTriggered != null) { + try { + whenTriggered.run(); + } catch (Throwable t) { + Strongback.logger(Fuse.class).error(t, "Error when calling fuse trigger function"); + } + } + return this; + } + + @Override + public Fuse reset() { + return this; + } + + @Override + public String toString() { + return "notTriggered"; + } + }; + } + /** * Create a fuse that can be manually {@link #reset()} but that will automatically reset after the given delay. * @@ -82,6 +121,19 @@ public String toString() { * @return the auto-resetting fuse; never null */ public static Fuse autoResetting(long delay, TimeUnit unit, Clock clock) { + return autoResetting(delay, unit, clock, null); + } + + /** + * Create a fuse that can be manually {@link #reset()} but that will automatically reset after the given delay. + * + * @param delay the time after the fuse is triggered that it should automatically reset; must be positive + * @param unit the time units for the delay; may not be null + * @param clock the clock that the fuse should use; if null, the system clock will be used + * @param whenTriggered the function that is to be called when the fuse is triggered; may be null + * @return the auto-resetting fuse; never null + */ + public static Fuse autoResetting(long delay, TimeUnit unit, Clock clock, Runnable whenTriggered) { Clock theClock = clock != null ? clock : Clock.system(); if (unit == null) throw new IllegalArgumentException("The time unit may not be null"); long delayTimeInMillis = unit.toMillis(delay); @@ -99,6 +151,13 @@ public synchronized boolean isTriggered() { public synchronized Fuse trigger() { triggered = true; resetTime = theClock.currentTimeInMillis() + delayTimeInMillis; + if (whenTriggered != null) { + try { + whenTriggered.run(); + } catch (Throwable t) { + Strongback.logger(Fuse.class).error(t, "Error when calling fuse trigger function"); + } + } return this; } diff --git a/strongback/src/org/strongback/components/PneumaticsModule.java b/strongback/src/org/strongback/components/PneumaticsModule.java new file mode 100644 index 0000000..85ba58c --- /dev/null +++ b/strongback/src/org/strongback/components/PneumaticsModule.java @@ -0,0 +1,101 @@ +/* + * 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.components; + +/** + * A simple abstraction for the Pneumatics Control Module (PCM). + */ +public interface PneumaticsModule { + + /** + * Gets the sensor that reports the current draw of the compressor. + * + * @return the current sensor for the compressor; never null + */ + public CurrentSensor compressorCurrent(); + + /** + * Get the switch that reports whether the compressor is running. + * + * @return the compressor switch; never null + */ + public Switch compressorRunningSwitch(); + + /** + * Get the switch that reports whether the pressure is below the lower threshold. + * + * @return the low pressure switch; never null + */ + public Switch lowPressureSwitch(); + + /** + * Get the relay that controls whether the module automatically runs the compressor when the {@link #lowPressureSwitch()} is + * triggered and shuts if off when the switch is untriggered (i.e., the maximum pressure threshold is reached). + * + * @return the automatic mode relay; never null + */ + public Relay automaticMode(); + + /** + * Get the faults currently associated with the compressor. These state of these faults may change at any time based upon + * the state of the module, and so they cannot be manually cleared. + * + * @return the current faults; never null + * @see #compressorStickyFaults() + */ + public Faults compressorFaults(); + + /** + * Get the sticky faults associated with the compressor. Once these faults are triggered, they are only reset with + * #clearStickyFaults + * + * @return the sticky faults; never null + * @see #compressorFaults() + * @see #clearStickyFaults() + */ + public Faults compressorStickyFaults(); + + /** + * Clear all {@link #compressorStickyFaults() sticky compressor faults} that may have been triggered. + * @return this instance so that methods can be chained; never null + */ + public PneumaticsModule clearStickyFaults(); + + /** + * The set of possible faults that this module can trigger. + */ + public static interface Faults { + + /** + * The switch that is {@link Switch#isTriggered() triggered} when the compressor is not connected. + * @return the switch; never null + */ + Switch notConnected(); + + /** + * The switch that is {@link Switch#isTriggered() triggered} when the compressor current is too high. + * @return the switch; never null + */ + Switch currentTooHigh(); + + /** + * The switch that is {@link Switch#isTriggered() triggered} when the compressor is not running because it is shorted. + * @return the switch; never null + */ + Switch shorted(); + } +} \ No newline at end of file diff --git a/strongback/src/org/strongback/components/Relay.java b/strongback/src/org/strongback/components/Relay.java index d8bbc8e..4bd7879 100644 --- a/strongback/src/org/strongback/components/Relay.java +++ b/strongback/src/org/strongback/components/Relay.java @@ -16,6 +16,9 @@ package org.strongback.components; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + import org.strongback.annotation.ThreadSafe; /** @@ -54,6 +57,7 @@ static enum State { /** * Get the current state of this relay. + * * @return the current state; never null */ State state(); @@ -132,4 +136,34 @@ public Relay off() { } }; } + + /** + * Obtain a relay that instantaneously switches from one state to another using the given functions. + * + * @param switcher the function that switches the state, where true represents {@link State#ON} and + * false represents {@link State#OFF}; may not be null + * @param onState the function that returns true if the current state is {@link State#ON}, or + * false otherwise; may not be null + * @return the relay; never null + */ + static Relay instantaneous(Consumer switcher, BooleanSupplier onState) { + return new Relay() { + @Override + public State state() { + return onState.getAsBoolean() ? State.ON : State.OFF; + } + + @Override + public Relay on() { + switcher.accept(Boolean.TRUE); + return this; + } + + @Override + public Relay off() { + switcher.accept(Boolean.FALSE); + return this; + } + }; + } } \ No newline at end of file diff --git a/strongback/src/org/strongback/hardware/Hardware.java b/strongback/src/org/strongback/hardware/Hardware.java index 7f671fd..e1ed4bb 100644 --- a/strongback/src/org/strongback/hardware/Hardware.java +++ b/strongback/src/org/strongback/hardware/Hardware.java @@ -21,6 +21,7 @@ import org.strongback.components.DistanceSensor; import org.strongback.components.Gyroscope; import org.strongback.components.Motor; +import org.strongback.components.PneumaticsModule; import org.strongback.components.PowerPanel; import org.strongback.components.Relay; import org.strongback.components.Solenoid; @@ -42,6 +43,7 @@ import edu.wpi.first.wpilibj.AnalogTrigger; import edu.wpi.first.wpilibj.BuiltInAccelerometer; import edu.wpi.first.wpilibj.CANTalon; +import edu.wpi.first.wpilibj.Compressor; import edu.wpi.first.wpilibj.DigitalInput; import edu.wpi.first.wpilibj.DoubleSolenoid; import edu.wpi.first.wpilibj.Encoder; @@ -83,6 +85,26 @@ public static PowerPanel powerPanel() { return PowerPanel.create(pdp::getCurrent, pdp::getTotalCurrent, pdp::getVoltage, pdp::getTemperature); } + /** + * Gets the {@link PneumaticsModule} of the robot with the default CAN ID of 0. + * + * @return the {@link PneumaticsModule} of the robot; never null + */ + public static PneumaticsModule pneumaticsModule() { + return new HardwarePneumaticsModule(new Compressor()); + } + + /** + * Gets the {@link PneumaticsModule} of the robot with the supplied CAN ID. Multiple pneumatics modules can be used by + * specifying the correct CAN ID of each module. + * + * @param canID the CAN ID of the module + * @return the {@link PneumaticsModule} of the robot; never null + */ + public static PneumaticsModule pneumaticsModule(int canID) { + return new HardwarePneumaticsModule(new Compressor(canID)); + } + /** * Factory method for angle sensors. */ diff --git a/strongback/src/org/strongback/hardware/HardwarePneumaticsModule.java b/strongback/src/org/strongback/hardware/HardwarePneumaticsModule.java new file mode 100644 index 0000000..a84214c --- /dev/null +++ b/strongback/src/org/strongback/hardware/HardwarePneumaticsModule.java @@ -0,0 +1,107 @@ +/* + * 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 org.strongback.components.CurrentSensor; +import org.strongback.components.PneumaticsModule; +import org.strongback.components.Relay; +import org.strongback.components.Switch; + +import edu.wpi.first.wpilibj.Compressor; + +/** + * A {@link PneumaticsModule} implementation based upon the WPILib's {@link Compressor} class, which represents the Pneumatics + * Control Module. + * + * @author Randall Hauch + */ +public class HardwarePneumaticsModule implements PneumaticsModule { + + private final Compressor pcm; + private final Relay closedLoop; + private final Faults instantaneousFaults; + private final Faults stickyFaults; + + HardwarePneumaticsModule(Compressor pcm) { + this.pcm = pcm; + this.closedLoop = Relay.instantaneous(this.pcm::setClosedLoopControl, this.pcm::getClosedLoopControl); + this.instantaneousFaults = new Faults() { + @Override + public Switch currentTooHigh() { + return pcm::getCompressorCurrentTooHighFault; + } + @Override + public Switch notConnected() { + return pcm::getCompressorNotConnectedFault; + } + @Override + public Switch shorted() { + return pcm::getCompressorShortedFault; + } + }; + this.stickyFaults = new Faults() { + @Override + public Switch currentTooHigh() { + return pcm::getCompressorCurrentTooHighStickyFault; + } + @Override + public Switch notConnected() { + return pcm::getCompressorNotConnectedStickyFault; + } + @Override + public Switch shorted() { + return pcm::getCompressorShortedStickyFault; + } + }; + } + + @Override + public CurrentSensor compressorCurrent() { + return pcm::getCompressorCurrent; + } + + @Override + public Switch compressorRunningSwitch() { + return pcm::enabled; + } + + @Override + public Relay automaticMode() { + return closedLoop; + } + + @Override + public Switch lowPressureSwitch() { + return pcm::getPressureSwitchValue; + } + + @Override + public Faults compressorFaults() { + return instantaneousFaults; + } + + @Override + public Faults compressorStickyFaults() { + return stickyFaults; + } + + @Override + public PneumaticsModule clearStickyFaults() { + pcm.clearAllPCMStickyFaults(); + return this; + } +}