@ThreadSafe
public interface Solenoid extends Requirable {
/**
* The direction of the solenoid.
*/
static enum Direction {
/** The solenoid is extending. */
EXTENDING,
/** The solenoid is retracting. */
RETRACTING,
/** The solenoid is stopped. */
STOPPED;
}
/**
* Get the current direction of this solenoid.
* @return the current direction; never null
*/
Direction getDirection();
/**
* Extends this solenoid.
* @return this object to allow chaining of methods; never null
*/
Solenoid extend();
/**
* Retracts this solenoid.
* @return this object to allow chaining of methods; never null
*/
Solenoid retract();
/**
* Determine if this solenoid is or was extending.
*
* @return true if this solenoid is in the process of extending but not yet
* fully extended, or false otherwise.
*/
default boolean isExtending() {...}
/**
* Determine if this solenoid is or was retracting.
*
* @return true if this solenoid is in the process of retracting but not yet
* fully extended, or false otherwise.
*/
default boolean isRetracting() {...}
/**
* Determine if this solenoid is stopped.
*
* @return {@code true} if this solenoid is not retracting or extending, or
* false otherwise
*/
default boolean isStopped() {...}
}
Solenoid
A hardware solenoid is any actuation device that can be extended or retracted. Examples include pneumatic cylinders and electrical solenoids.
This section describes Strongback’s multiple interfaces that represent physical solenoids.
Solenoid
The org.strongback.components.Solenoid
interface represents a simple actuator device that can extend or retract. Many solenoids will be nearly instantaneous, although others may be extending or retracting for more than one control cycle.
Most of the methods should be easy to understand and use. The getDirection()
method returns an enumeration that represents last known direction commanded to the solenoid.
SolenoidWithPosition
The org.strongback.components.SolenoidWithPosition
interface is a type of Solenoid
that is able to monitor its own position. An example might be a pneumatic cylinder with magnetic reed switches.
@ThreadSafe
public interface SolenoidWithPosition extends Solenoid {
/**
* The possible positions for a limited motor.
*/
public enum Position {
/** The motor is fully extended. **/
EXTENDED,
/** The motor is fully retracted. **/
RETRACTED,
/** The motor is not fully retracted or fully extended, but the exact position is unknown. **/
UNKNOWN
}
/**
* Get the current position of this solenoid.
*
* @return the current position; never null
*/
public Position getPosition();
/**
* Determines if this Solenoid is extended.
*
* @return true if this solenoid is fully extended, or false otherwise
*/
default public boolean isExtended() {...}
/**
* Determines if this Solenoid is retracted.
*
* @return true if this solenoid is fully retracted, or false otherwise
*/
default public boolean isRetracted() {...}
/**
* Create a solenoid that uses the supplied function to determine the position.
* @param solenoid the solenoid; may not be null
* @param positionSupplier the function that returns the position; may not be null
* @return the {@link SolenoidWithPosition} instance; never null
*/
public static SolenoidWithPosition create(Solenoid solenoid,
Supplier<Position> positionSupplier ) {...}
/**
* Create a solenoid that uses the two given switches to determine position.
* @param solenoid the solenoid; may not be null
* @param retractSwitch the switch that determines if the solenoid is retracted; may not be null
* @param extendSwitch the switch that determines if the solenoid is extended; may not be null
* @return the {@link SolenoidWithPosition} instance; never null
*/
public static SolenoidWithPosition create(Solenoid solenoid,
Switch retractSwitch,
Switch extendSwitch ) {...}
}
Again, the methods are pretty straightforward.
The create(…)
static factory methods make it easy to create a SolenoidWithPosition
from a Solenoid
and one or two Switch
objects.
Relay
The org.strongback.components.Relay
interface that represents a device that can be turned on and off. A common example of a relay is the VEX Robotics Spike style relay used in FRC with pneumatic cylinders.
Note that a relay has one of 5 possible states:
-
ON - the relay is in the "on" position;
-
OFF - the relay is in the "off" position;
-
SWITCHING_ON - the relay was in the "off" position but has been changed and is not yet in the "on" position;
-
SWITCHING_OFF - the relay was in the "on" position but has been changed and is not yet in the "off" position; and
-
UNKNOWN - the relay position is not known
Not all Relay implementations use all relay states. Very simple relays that have no delay will simply only use State.ON
and State.OFF
, while relays that have some delay might also use State.SWITCHING_ON
and State.SWITCHING_OFF
. Those relay implementations that may not know their position upon startup may start out in the UNKNOWN
state.
public interface Relay {
static enum State {
/**
* The relay is presently switching into the "ON" state but has
* not yet completed the change.
*/
SWITCHING_ON,
/**
* The relay is presently in the "on" position.
*/
ON,
/**
* The relay is presently switching into the "OFF" state but has
* not yet completed the change.
*/
SWITCHING_OFF,
/**
* The relay is presently in the "off" position.
*/
OFF,
/**
* The actual state of the relay is not known.
*/
UNKOWN
}
/**
* Get the current state of this relay.
* @return the current state; never null
*/
State state();
/**
* Turn on this relay.
*
* @return this object to allow chaining of methods; never null
*/
Relay on();
/**
* Turn off this relay.
*
* @return this object to allow chaining of methods; never null
*/
Relay off();
/**
* Check whether this relay is known to be on. This is equivalent to calling
* `state() == State.ON`.
*
* @return true if this relay is on; or false otherwise
*/
default boolean isOn() {...}
/**
* Check whether this relay is known to be off. This is equivalent to calling
* `state() == State.OFF`.
*
* @return true if this relay is off; or false otherwise
*/
default boolean isOff() {...}
/**
* Check if this relay is switching on. This is equivalent to calling
* `state() == State.SWITCHING_ON`.
*
* @return true if this relay is in the process of switching from off to on;
* or false otherwise
*/
default boolean isSwitchingOn() {...}
/**
* Check if this relay is switching off. This is equivalent to calling
* `state() == State.SWITCHING_OFF`.
*
* @return true if this relay is in the process of switching from on to off;
* or false otherwise
*/
default boolean isSwitchingOff() {...}
/**
* Obtain a relay that remains in one fixed state, regardless of any
* calls to on() or off().
*
* @param state the fixed state; may not be null
* @return the constant relay; never null
*/
static Relay fixed(State state) {...}
}
Conceptually a relay is similar to a Solenoid
, although semantics are slightly different.
Obtaining hardware solenoids and relays
Strongback provides a org.strongback.hardware.Hardware
class with static factory methods for physical hardware solenoids and relays:
public class Hardware {
...
/**
* Factory methods for solenoids.
*/
public static final class Solenoids {
/**
* Create a double-acting solenoid that uses the specified channels on the default module.
*
* @param extendChannel the channel that extends the solenoid
* @param retractChannel the channel that retracts the solenoid
* @param initialDirection the initial direction for the solenoid; may not be null
* @return a solenoid on the specified channels; never null
*/
public static Solenoid doubleSolenoid(int extendChannel,
int retractChannel,
Solenoid.Direction initialDirection) {...}
/**
* Create a double-acting solenoid that uses the specified channels on the given module.
*
* @param module the module for the channels
* @param extendChannel the channel that extends the solenoid
* @param retractChannel the channel that retracts the solenoid
* @param initialDirection the initial direction for the solenoid; may not be null
* @return a solenoid on the specified channels; never null
*/
public static Solenoid doubleSolenoid(int module,
int extendChannel,
int retractChannel,
Solenoid.Direction initialDirection) {...}
/**
* Create a relay on the specified channel.
*
* @param channel the channel the relay is connected to
* @return a relay on the specified channel
*/
public static Relay relay(int channel) {...}
}
...
}
Obviously you should only use this class in code that will only run on the robot, so we recommend centralizing this logic inside one of your top-level robot classes:
public class SimpleRobot extends IterativeRobot {
private Solenoid cylinder;
@Override
public void robotInit() {
...
// pressure solenoid in channels 3 and 4
Solenoid actualCylinder = Hardare.Solenoids.doubleSolenoid(3,4);
// reed switches in channels 5 and 6
Switch retracted = Hardware.Switches.normallyOpen(5);
Switch extended = Hardware.Switches.normallyOpen(5);
cylinder = SolenoidWithPosition.create(actualCylinder,retracted,extended);
// pass 'cylinder' to the objects that need it ...
}
You can then pass the switches around to other components that need them.
Tip
|
Testability tip
You might think that it’s easy for components to just get the Instead, it’s much better to use injection, which is just a fancy way of saying that when a component is created it should be handed all the objects it needs. So if we have a component that requires a sensor, then that component should require the sensor be given to it (usually through the constructor, if possible). The robot class can create this component and pass the sensor to it, while test cases can pass in a mock sensor. (We’ll talk about mocks in the next section.) The bottom line is that these other components should depend only upon injected |
Using switches in tests
Your tests should never use the Hardware class to create solenoids in your tests. Instead, Strongback provides a series of mock classes that can be used in place of the hardware implementations. The mock solenoid extend the Solenoid
interface as expected, and normally this will be all your tests need. However, the MockSolenoid
class does add a stop()
method that stops the solenoid in whatever position it is (should that be necessary).
The MockSolenoid
class is defined as follows:
public class MockSolenoid implements Solenoid {
private volatile Direction direction = Direction.STOPPED;
private final boolean completeImmediately;
protected MockSolenoid( boolean completeImmediately ) {
this.completeImmediately = completeImmediately;
}
@Override
public MockSolenoid extend() {
direction = Direction.EXTENDING;
if ( completeImmediately ) direction = Direction.STOPPED;
return this;
}
@Override
public MockSolenoid retract() {
direction = Direction.EXTENDING;
if ( completeImmediately ) direction = Direction.STOPPED;
return this;
}
@Override
public Direction getDirection() {
return direction;
}
/**
* Stop any movement of this solenoid.
* @return this object so that methods can be chained together; never null
*/
public MockSolenoid stop() {
direction = Direction.STOPPED;
return this;
}
}
You can create mock objects using the org.strongback.mock.Mock
class' static factory methods. For example, here’s a fragment from a test that creates a few instantaneous solenoids:
Solenoid s1 = Mock.instantaneousSolenoid();
s1.extend();
assert s1.isExtending();
There is no mock SolenoidWithPosition
; instead, just create a mock solenoid with one or two mock switches:
Solenoid s1 = Mock.instantaneousSolenoid();
MockSwitch extended = Mock.notTriggered();
MockSwitch retracted = Mock.triggered();
SolenoidWithPosition cylinder = SolenoidWithPosition.create(s1,retracted,extended);
cylinder.extend();
assertThat(extended.isTriggered()).isTrue();
The MockRelay
class is defined as follows:
public class MockRelay implements Relay {
private State state;
@Override
public MockRelay off() {
state = State.OFF;
return this;
}
@Override
public MockRelay on() {
state = State.ON;
return this;
}
/**
* Set the state of this relay to State.SWITCHING_OFF.
* @return this instance to enable chaining methods; never null
*/
public MockRelay switchingOff() {
state = State.SWITCHING_OFF;
return this;
}
/**
* Set the state of this relay to State.SWITCHING_ON.
* @return this instance to enable chaining methods; never null
*/
public MockRelay switchingOn() {
state = State.SWITCHING_ON;
return this;
}
@Override
public State state() {
return state;
}
}
Using the mock relay is easy, although you’ll likely do this in tests of components that use the relay.
MockRelay relay = Mock.relay().on();